Home > Mobile >  Show flutter overlay according to different variables
Show flutter overlay according to different variables

Time:01-25

I have a flutter screen called TestMain which has a scaffold and white background. The scaffolds body is supposed to change if certain events happen. The events are stored as a boolean. There is "isLocked" and "isPaused" which get emitted by a Riverpod Stream Provider and "isCheating" which changes when Applifecyle events get triggered. All of the three booleans are stored as Riverpod StateProviders, because of its global accesibility.

This is is my "isCheatingProvider": final isCheatingProvider = StateProvider.autoDispose<bool>((ref) => false); The "isPausedProvider" and "isLockedProvider" are the same.

This is the TestMain screen

class TestMain extends ConsumerStatefulWidget {
  const TestMain({super.key});

  @override
  ConsumerState<TestMain> createState() => _TestMainScreenState();
}

class _TestMainScreenState extends ConsumerState<TestMain>
    with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) async {
    super.didChangeAppLifecycleState(state);

    final isCheating = ref.watch(isCheatingProvider.notifier);

    switch (state) {
      case AppLifecycleState.resumed:
      case AppLifecycleState.inactive:
        await sendCheatingAttempt(ref);
        setState(() {
          isCheating.state = true;
        });
        break;
      case AppLifecycleState.paused:
        await sendCheatingAttempt(ref);
        setState(() {
          isCheating.state = true;
        });
        break;
      case AppLifecycleState.detached:
        await sendCheatingAttempt(ref);
        setState(() {
          isCheating.state = true;
        });
        break;
    }
  }

  @override
  Widget build(BuildContext context) {
    final List<Item> items = ref.watch(itemsProvider);
    final AsyncValue<dynamic> wsTestListenerMessage =
        ref.watch(testListenerProvider);

    final isLocked = ref.watch(isLockedProvider.notifier);
    final isPaused = ref.watch(isPausedProvider.notifier);
    final isCheating = ref.watch(isCheatingProvider.notifier);

    wsTestListenerMessage.when(
      loading: () => {},
      error: (err, stack) => print('Test State Error: $err'),
      data: (message) async {
        Future.delayed(const Duration(seconds: 0), () {
          if (message["lock"] == true) {
            isLocked.state = true;
          }
          if (message["unlock"] == true) {
            isLocked.state = false;
          }
          if (message["paused"] == true) {
            isPaused.state = true;
          }
          if (message["resumed"] == true) {
            isPaused.state = false;
          }
        });
      },
    );

    return Scaffold(
      backgroundColor: Colors.white,
      body: SafeArea(
          child: isPaused.state
              ? const ErrorOverlay(text: 'paused')
              : isLocked.state || isCheating.state
                  ? const ErrorOverlay(text: 'cheating')
                  : const TestView()),
    );
  }
}

But it doesnt work. No matter what I do. I added the Future.delayed(const Duration(seconds: 0), () {} around the if-statements, because it complained about changing the provider in build method, I use setState() in didChangeAppLifecycleState(), but can't use it in the listener, because the listener would called over and over again. It shouldnt be openend more than once.

(ErrorOverlay is a custom widget that just shows the text in big red letters, in the center)

CodePudding user response:

  • remove the setState, this will do nothing
  • for set a state use ref.read(provider.notifier).state
  • for watch use ref.watch(isCheatingProvider)

By changing all that it is good by testing on my side :

final isCheatingProvider = StateProvider.autoDispose<bool>((ref) => false);

class TestMain extends ConsumerStatefulWidget {
  const TestMain({key});

  @override
  ConsumerState<TestMain> createState() => _TestMainScreenState();
}

class _TestMainScreenState extends ConsumerState<TestMain>
    with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) async {
    super.didChangeAppLifecycleState(state);

    final isCheating = ref.read(isCheatingProvider.notifier);

    switch (state) {
      case AppLifecycleState.resumed:
      case AppLifecycleState.inactive:
        isCheating.state = true;
        break;
      case AppLifecycleState.paused:
        isCheating.state = true;
        break;
      case AppLifecycleState.detached:
        isCheating.state = true;
        break;
    }
  }

  @override
  Widget build(BuildContext context) {
    final isCheating = ref.watch(isCheatingProvider);

    return Scaffold(
      backgroundColor: isCheating ? Colors.red : Colors.white,
      body: SafeArea(
          child: isCheating ? Text('cheat') : Text(' good')
      )
    );
  }
}

CodePudding user response:

You are incorrectly using StateProvider. To watch StateNotifier you should use

final isCheating = ref.watch(isCheatingProvider);

and to change provider use

ref.read(productSortTypeProvider.notifier).state = value;

So you have to change all provider related code.

@override
void didChangeAppLifecycleState(AppLifecycleState state) async {
  super.didChangeAppLifecycleState(state);

  final isCheatingNotifier = ref.read(isCheatingProvider.notifier);

  switch (state) {
    case AppLifecycleState.resumed:
    case AppLifecycleState.inactive:
      await sendCheatingAttempt(ref);
      isCheatingNotifier.state = true;
      break;
    case AppLifecycleState.paused:
      await sendCheatingAttempt(ref);
      isCheatingNotifier.state = true;
      break;
    case AppLifecycleState.detached:
      await sendCheatingAttempt(ref);
      isCheatingNotifier.state = true;
      break;
  }
}

@override
Widget build(BuildContext context) {
  final List<Item> items = ref.watch(itemsProvider);
  final AsyncValue<dynamic> wsTestListenerMessage =
  ref.watch(testListenerProvider);

  final isLocked = ref.watch(isLockedProvider);
  final isPaused = ref.watch(isPausedProvider);
  final isCheating = ref.watch(isCheatingProvider);

  wsTestListenerMessage.when(
    loading: () => {},
    error: (err, stack) => print('Test State Error: $err'),
    data: (message) async {
      Future.delayed(const Duration(seconds: 0), () {
        final isLockedNotifier = ref.read(isLockedProvider.notifier);
        final isPausedNotifier = ref.read(isPausedProvider.notifier);

        if (message["lock"] == true) {
          isLockedNotifier.state = true;
        }
        if (message["unlock"] == true) {
          isLockedNotifier.state = false;
        }
        if (message["paused"] == true) {
          isPausedNotifier.state = true;
        }
        if (message["resumed"] == true) {
          isPausedNotifier.state = false;
        }
      });
    },
  );

  return Scaffold(
    backgroundColor: Colors.white,
    body: SafeArea(
        child: isPaused
            ? const ErrorOverlay(text: 'paused')
            : isLocked || isCheating
            ? const ErrorOverlay(text: 'cheating')
            : const TestView()),
  );
}
  • Related