Line data Source code
1 : import 'dart:async'; 2 : 3 : import 'package:connectivity_plus/connectivity_plus.dart'; 4 : import 'package:flutter/foundation.dart'; 5 : import 'package:flutter/material.dart'; 6 : import 'package:stream_chat/stream_chat.dart'; 7 : import 'package:stream_chat_flutter_core/src/typedef.dart'; 8 : 9 : /// Widget used to provide information about the chat to the widget tree. 10 : /// This Widget is used to react to life cycle changes and system updates. 11 : /// When the app goes into the background, the websocket connection is kept 12 : /// alive for two minutes before being terminated. 13 : /// 14 : /// Conversely, when app is resumed or restarted, a new connection is initiated. 15 : /// 16 : /// ```dart 17 : /// class MyApp extends StatelessWidget { 18 : /// final StreamChatClient client; 19 : /// 20 : /// MyApp(this.client); 21 : /// 22 : /// @override 23 : /// Widget build(BuildContext context) { 24 : /// return MaterialApp( 25 : /// home: Container( 26 : /// child: StreamChatCore( 27 : /// client: client, 28 : /// child: ChannelListPage(), 29 : /// ), 30 : /// ), 31 : /// ); 32 : /// } 33 : /// } 34 : /// ``` 35 : /// 36 : class StreamChatCore extends StatefulWidget { 37 : /// Constructor used for creating a new instance of [StreamChatCore]. 38 : /// 39 : /// [StreamChatCore] is a stateful widget which reacts to system events and 40 : /// updates Stream's connection status accordingly. 41 7 : const StreamChatCore({ 42 : Key? key, 43 : required this.client, 44 : required this.child, 45 : this.onBackgroundEventReceived, 46 : this.backgroundKeepAlive = const Duration(minutes: 1), 47 : this.connectivityStream, 48 7 : }) : super(key: key); 49 : 50 : /// Instance of Stream Chat Client containing information about the current 51 : /// application. 52 : final StreamChatClient client; 53 : 54 : /// Widget descendant. 55 : final Widget child; 56 : 57 : /// The amount of time that will pass before disconnecting the client in 58 : /// the background 59 : final Duration backgroundKeepAlive; 60 : 61 : /// Handler called whenever the [client] receives a new [Event] while the app 62 : /// is in background. Can be used to display various notifications depending 63 : /// upon the [Event.type] 64 : final EventHandler? onBackgroundEventReceived; 65 : 66 : @visibleForTesting 67 : final Stream<ConnectivityResult>? connectivityStream; 68 : 69 7 : @override 70 7 : StreamChatCoreState createState() => StreamChatCoreState(); 71 : 72 : /// Use this method to get the current [StreamChatCoreState] instance 73 6 : static StreamChatCoreState of(BuildContext context) { 74 : StreamChatCoreState? streamChatState; 75 : 76 6 : streamChatState = context.findAncestorStateOfType<StreamChatCoreState>(); 77 : 78 : assert( 79 3 : streamChatState != null, 80 : 'You must have a StreamChat widget at the top of your widget tree', 81 : ); 82 : 83 : return streamChatState!; 84 : } 85 : } 86 : 87 : /// State class associated with [StreamChatCore]. 88 : class StreamChatCoreState extends State<StreamChatCore> 89 : with WidgetsBindingObserver { 90 : /// Initialized client used throughout the application. 91 21 : StreamChatClient get client => widget.client; 92 : 93 : Timer? _disconnectTimer; 94 : 95 7 : @override 96 14 : Widget build(BuildContext context) => widget.child; 97 : 98 : /// The current user 99 4 : User? get user => client.state.user; 100 : 101 : /// The current user as a stream 102 4 : Stream<User?> get userStream => client.state.userStream; 103 : 104 : late final StreamSubscription<ConnectivityResult> _connectivitySubscription; 105 : 106 : var _isInForeground = true; 107 : var _isConnectionAvailable = true; 108 : 109 7 : @override 110 : void initState() { 111 7 : super.initState(); 112 14 : WidgetsBinding.instance?.addObserver(this); 113 7 : _connectivitySubscription = 114 28 : (widget.connectivityStream ?? Connectivity().onConnectivityChanged) 115 8 : .listen((ConnectivityResult result) async { 116 2 : _isConnectionAvailable = result != ConnectivityResult.none; 117 1 : if (!_isInForeground) { 118 : return; 119 : } 120 1 : if (_isConnectionAvailable) { 121 3 : if (client.wsConnectionStatus == ConnectionStatus.disconnected) { 122 3 : await client.connect(); 123 : } 124 : } else { 125 3 : if (client.wsConnectionStatus == ConnectionStatus.connected) { 126 3 : await client.disconnect(); 127 : } 128 : } 129 : }); 130 : } 131 : 132 : StreamSubscription? _eventSubscription; 133 : 134 1 : @override 135 : void didChangeAppLifecycleState(AppLifecycleState state) { 136 2 : _isInForeground = state == AppLifecycleState.resumed; 137 1 : if (user != null) { 138 1 : if (!_isInForeground) { 139 1 : _onBackground(); 140 : } else { 141 1 : _onForeground(); 142 : } 143 : } 144 : } 145 : 146 1 : void _onForeground() { 147 3 : if (_disconnectTimer?.isActive == true) { 148 2 : _eventSubscription?.cancel(); 149 2 : _disconnectTimer?.cancel(); 150 3 : } else if (client.wsConnectionStatus == ConnectionStatus.disconnected && 151 1 : _isConnectionAvailable) { 152 2 : client.connect(); 153 : } 154 : } 155 : 156 1 : void _onBackground() { 157 2 : if (widget.onBackgroundEventReceived == null) { 158 3 : if (client.wsConnectionStatus != ConnectionStatus.disconnected) { 159 2 : client.disconnect(); 160 : } 161 : return; 162 : } 163 4 : _eventSubscription = client.on().listen( 164 2 : widget.onBackgroundEventReceived, 165 : ); 166 : 167 1 : void onTimerComplete() { 168 2 : _eventSubscription?.cancel(); 169 2 : client.disconnect(); 170 : } 171 : 172 4 : _disconnectTimer = Timer(widget.backgroundKeepAlive, onTimerComplete); 173 : return; 174 : } 175 : 176 7 : @override 177 : void dispose() { 178 14 : WidgetsBinding.instance?.removeObserver(this); 179 8 : _eventSubscription?.cancel(); 180 8 : _disconnectTimer?.cancel(); 181 14 : _connectivitySubscription.cancel(); 182 7 : super.dispose(); 183 : } 184 : }