I am working on a flutter web project and want to get the status of the user (online?). I use WidgetsBindingObserver, but didChangeAppLifecycleState doesn't work on the web.
I tried this code.
class HomePage extends StatefulWidget {
const HomePage({super.key});
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>with WidgetsBindingObserver {
#override
void didChangeAppLifecycleState(AppLifecycleState state){
if(state == AppLifecycleState.resumed){
//is online
}else{
//isn't
}
}
#override
void initState() {
// TODO: implement initState
if (kIsWeb) {
window.addEventListener('focus', onFocus);
window.addEventListener('blur', onBlur);
} else {
WidgetsBinding.instance.addObserver(this);
}
super.initState();
}
#override
void dispose() {
if (kIsWeb) {
window.removeEventListener('focus', onFocus);
window.removeEventListener('blur', onBlur);
} else {
WidgetsBinding.instance.removeObserver(this);
}
super.dispose();
}
void onFocus(Event e) {
didChangeAppLifecycleState(AppLifecycleState.resumed);
}
void onBlur(Event e) {
didChangeAppLifecycleState(AppLifecycleState.paused);
}
#override
Widget build(BuildContext context) {
return Scaffold(body: Container(child:Text('hello') ));
}
}
When you focus and blur the web page, it works. The user is still online if you close the tab or browser. To no avail, I tried setting it to outline in the dispose method.
Related
I am building a QR code scanner app with a couple of tabs wrapped up within CupertinoTabBar in a CupertinoTabScaffold. I have a CupertinoTabController to take care of the switching between the tabs. One of this tabs has a CameraPreview widget from the Camera plugin of Flutter along with a proper dispose mechanism. However, whenever the tab are switched, the Camera stream still persists, causing the phone to heat up and also causes janky UX. Now I read that the BottomNavigationBar from Material widgets does not persist in this way. Any idea on how to achieve the same behaviour with CupertinoTabBar?
You can use the StatefulWidget for each a page of the tabs and then try to listening AppLifecycleState. Disponse controller if state inactive/paused.
In my case it's working fine.
class Example extends StatefulWidget {
#override
ExampleState createState() => ExampleState();
}
//Implement WidgetsBindingObserver to listen Lifecycle State
class ExampleState extends State<Example> with WidgetsBindingObserver {
late CameraController _controller;
...
...
#override
void initState() {
super.initState();
// Add Listener (Lifecycle State)
WidgetsBinding.instance!.addObserver(this);
}
Future<void> _setupController() async {
//todo setup/init controller
}
//Implements this method to listen Lifecycle State
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
_controller.dispose();
_setupCameraAndControllerFuture = _setupController();
}
if (state == AppLifecycleState.inactive) {
_controller.dispose();
} else if (state == AppLifecycleState.paused) {
_controller.dispose();
}
}
#override
void dispose() {
// Remove Listener (Lifecycle State)
WidgetsBinding.instance!.removeObserver(this);
// dispose controller
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
...
...
);
}
}
I am using Flutter with hooks and I am trying to get the App Life Cycle State. I followed documentation and created new hook (code shown below) which works ok for all situations with one exception. When the application state becomes "paused", the hook does not return the value back to the widget. I am not clear what to do at this point. Someone suggested using Isolates but I don't see how that can help. Updating App Life Cycle is not compute expensive.
Please let me know what else I could do make this work.
Thanks
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
AppLifecycleState useAppLifecycleState() {
return use(const _LifeCycleState());
}
class _LifeCycleState extends Hook<AppLifecycleState> {
const _LifeCycleState();
#override
__LifeCycleState createState() => __LifeCycleState();
}
class __LifeCycleState extends HookState<AppLifecycleState, _LifeCycleState>
with WidgetsBindingObserver {
AppLifecycleState _state;
#override
void initHook() {
super.initHook();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
super.dispose();
WidgetsBinding.instance.removeObserver(this);
}
#override
AppLifecycleState build(BuildContext context) {
return _state;
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
setState(() {
_state = state;
});
super.didChangeAppLifecycleState(state);
}
}
Thanks for your help.
How can I monitor the life cycle states of the app from a particular page using HookWidget the way you can with a Stateful widget?
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.paused) {
...
}
if (state == AppLifecycleState.resumed) {
...
}
if (state == AppLifecycleState.detached) {
...
}
}
First make a class:
class MyObserver implements WidgetsBindingObserver {
}
Then create it and register it with:
Widget build(BuildContext) {
useEffect(() {
final observer = MyObserver();
WidgetsBinding.instance.addObserver(observer);
return () => WidgetsBinding.instance.removeObserver(observer);
}, const []);
...
}
Flutter hooks is shipped with an inbuilt didchangeapplifecycle
access it as follows
final appLifecycleState = useAppLifecycleState();
useEffect(() {
print("current app state");
print(appLifecycleState);
if (appLifecycleState == AppLifecycleState.paused || appLifecycleState == AppLifecycleState.inactive) {
//...
} else if (appLifecycleState == AppLifecycleState.resumed) {
//...
}
return null;
}, [appLifecycleState]);
In the docs here search for "ways to create a hook". You'll see there are 2 ways of creating a hook, using a function or using a class. You are going for the "using a class" one. Then use initHook override as your initState and dispose works the same. Thats how I implemented it on my end.
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
useWidgetLifecycleObserver(BuildContext context) {
return use(const _WidgetObserver());
}
class _WidgetObserver extends Hook<void> {
const _WidgetObserver();
#override
HookState<void, Hook<void>> createState() {
return _WidgetObserverState();
}
}
class _WidgetObserverState extends HookState<void, _WidgetObserver> with WidgetsBindingObserver {
#override
void build(BuildContext context) {}
#override
void initHook() {
super.initHook();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
print("app state now is $state");
super.didChangeAppLifecycleState(state);
}
}
Then
class Root extends HookWidget {
#override
Widget build(BuildContext context) {
useWidgetLifecycleObserver(context);
I've just had to deal with the same problem. And here is my solution using custom hooks:
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
AppLifecycleState useAppLifecycleState() {
return use(const _LifeCycleState());
}
class _LifeCycleState extends Hook<AppLifecycleState> {
const _LifeCycleState();
#override
__LifeCycleState createState() => __LifeCycleState();
}
class __LifeCycleState extends HookState<AppLifecycleState, _LifeCycleState>
with WidgetsBindingObserver {
AppLifecycleState _theState;
#override
void initHook() {
super.initHook();
WidgetsBinding.instance.addObserver(this);
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
setState(() {
_theState = state;
});
}
#override
AppLifecycleState build(BuildContext context) {
return _theState;
}
#override
void dispose() {
super.dispose();
WidgetsBinding.instance.removeObserver(this);
}
}
And in the HookWidget that you want to access the app lifecycle state use the useEffect :
final appLifecycleState = useAppLifecycleState();
useEffect(() {
print("current app state");
print(appLifecycleState);
if (appLifecycleState == AppLifecycleState.paused ||
appLifecycleState == AppLifecycleState.inactive) {
//...
} else if (appLifecycleState == AppLifecycleState.resumed) {
//...
}
return null;
}, [appLifecycleState]);
After upgrading to Flutter 0.7.3 channel beta (Dart 2.1.0-dev.1.0.flutter-ccb16f7282) the WidgetsBindingObserver is not working.
It worked before and after login if AppLifecycleState paused, inactive or suspended it returns to main page. But seems it doesnt work with new update. My question is where I can get information to see how to make the WidgetsBindingObserver works again.
// Statefull HomePage
class PersonalLoginPage extends StatefulWidget {
const PersonalLoginPage({ Key key }) : super(key: key);
#override
_PersonalLoginPageState createState() => new _PersonalLoginPageState();
}
class _PersonalLoginPageState extends State<PersonalLoginPage> with WidgetsBindingObserver {
AppLifecycleState _appLifecycleState;
bool _appStatePause;
// TODO: initState function
#override
void initState() {
print("initState Starting Now .......................");
super.initState();
authenticateUser();
WidgetsBinding.instance.addObserver(this);
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
Timer _timer;
setState(() {
_appLifecycleState = state;
if (_appLifecycleState == AppLifecycleState.paused ||
_appLifecycleState == AppLifecycleState.inactive ||
_appLifecycleState == AppLifecycleState.suspending) {
_appStatePause = true;
print("New Timer Starting Now .......................");
_timer = Timer.periodic(Duration(seconds: 60), _callback);
} else {
_appStatePause = false;
}
});
}
void _callback(_timer) {
if (_appStatePause == true) {
print("Timer Finished without cancel...................");
setState(() {
Navigator.push(
context,
SlideRightRoute(widget: MyApp()),
);
});
} else {
_timer.cancel();
print("Timer cancel now................................");
}
}
// TODO: authenticateUser function
Future authenticateUser() async {
……
……
……
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
// TODO: main build Widget
#override
Widget build(BuildContext context) {
return Scaffold(
body: new Center(
child: new Text(“Hello World”)
)
);
}
Android's LiveData allows to update the UI when the activity is in an active state. So if a background operation has finished while the activity is paused, the activity won't be notified and thus the app won't crush. Can Flutter perform the same behavior?
For people interested in an equivalent of LiveData for other scenarios, I present you StreamController:
class ExampleViewModel {
StreamController<bool> loggedInStream = StreamController<bool>();
logIn() { loggedInStream.add(true); }
}
class ExampleScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() => ExampleScreenState();
}
class ExampleScreenState extends State<ExampleScreen> {
ExampleViewModel _viewModel;
BuildContext _ctx;
#override
void initState() {
super.initState();
_viewModel = ExampleViewModel()
_viewModel.loggedInStream.stream.listen( (loggedIn) {
if ( loggedIn != null && loggedIn ) {
Navigator.of(_ctx).pushReplacementNamed("/home");
}
});
}
#override
Widget build(BuildContext context) {
_ctx = context;
var loginBtn =
RaisedButton(
onPressed: _viewModel.logIn(true),
child: Text(
"LOGIN",
style: new TextStyle(
fontSize: 24.0,
)
),
color: Colors.green,
textColor: Colors.white,
);
return loginBtn;
}
#override
void dispose() {
super.dispose();
_viewModel.loggedInStream.close();
}
}
You can subscribe to it just like a LiveData, using:
loggedInStream.stream.listen( (data) { code } )
And you should clear the listeners in dispose to avoid memory leaks:
loggedInStream.close()
This code basically do the following things:
Creates a screen with a button.
Listen to a Stream (observe a LiveData).
When you click the button, it changes the value.
The listener (observer) is triggered.
Launches new screen.
You can use WidgetsBindingObserver to listen to the application state.
class AppLifecycleReactor extends StatefulWidget {
const AppLifecycleReactor({ Key key }) : super(key: key);
#override
_AppLifecycleReactorState createState() => new _AppLifecycleReactorState();
}
class _AppLifecycleReactorState extends State<AppLifecycleReactor> with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
AppLifecycleState _notification;
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
setState(() { _notification = state; });
}
#override
Widget build(BuildContext context) {
return new Text('Last notification: $_notification');
}
}
Easy: Flutterx Live Data
There is no need to observe App lifecycle: widget are built only when the app is resumend
This library integrates perfectly LiveData concepts, also is well documented.
Is developed on Flutter 1.14.x-dev you need master flutter channel at the moment
Sorry to be late for the party,
My colleague and I have developed a library that mimics the live data of android, on flutter.
Check it out:
https://pub.dev/packages/stream_live_data