skip to Main Content

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)

2

Answers


    • 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')
          )
        );
      }
    }
    
    Login or Signup to reply.
  1. 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()),
      );
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search