I'm trying to start a new screen within an AppLifecycleState event, but nothing happens. That's because there is no context available in this event that would contain Navigator.
The app must open LockScreen each time the app comes back from resume state (AppLifecycleState.resumed). The most simple case is a banking app that is protected with lock screen each time it's opened.
How to show new screen no matter where you are in code?
My code that does not work:
import 'package:alarm_prevozi/screens/home_screen/home_screen.dart';
import 'package:alarm_prevozi/screens/lock_screen/lock_screen.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:alarm_prevozi/helpers/translations.dart';
import 'package:flutter/material.dart';
void main() async {
// Then start the application
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
BuildContext myContext;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
// Listen for when the app enter in background or foreground state.
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
// user returned to our app
_showLockScreenDialog();
} else if (state == AppLifecycleState.inactive) {
// app is inactive
} else if (state == AppLifecycleState.paused) {
// user is about quit our app temporally
} else if (state == AppLifecycleState.suspending) {
// app suspended (not used in iOS)
}
}
// Main initialization
@override
Widget build(BuildContext context) {
return MaterialApp(
localizationsDelegates: [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
// Tells the system which are the supported languages
supportedLocales: translationService.supportedLocales(),
home: HomeScreen()
);
}
void _showLockScreenDialog() {
Navigator.of(context)
.pushReplacement(new MaterialPageRoute(builder: (BuildContext context) {
return PassCodeScreen();
}));
}
}
You could use a stream to handle the resume "event". Since your observer is above the MaterialApp, you don't have a Navigator in your MyApp context, so you need to use a GlobalKey to access the Navigator provided by the MaterialApp.
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
StreamController<bool> _showLockScreenStream = StreamController();
StreamSubscription _showLockScreenSubs;
GlobalKey<NavigatorState> _navigatorKey = GlobalKey();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_showLockScreenSubs = _showLockScreenStream.stream.listen((bool show){
if (mounted && show) {
_showLockScreenDialog();
}
});
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
_showLockScreenSubs?.cancel();
super.dispose();
}
// Listen for when the app enter in background or foreground state.
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
// user returned to our app, we push an event to the stream
_showLockScreenStream.add(true);
} else if (state == AppLifecycleState.inactive) {
// app is inactive
} else if (state == AppLifecycleState.paused) {
// user is about quit our app temporally
} else if (state == AppLifecycleState.detached) {
// detached from any host views
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: _navigatorKey,
...
);
}
void _showLockScreenDialog() {
_navigatorKey.currentState.
.pushReplacement(new MaterialPageRoute(builder: (BuildContext context) {
return PassCodeScreen();
}));
}
}