Search code examples
flutterlockscreen

Navigating to a new screen from AppLifecycleState.resumed event?


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();
    }));
  }
}

Solution

  • 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();
        }));
      }
    }