1

I try to use a StateNotifier with my own classes but whatever I do the state does not change. I use riverpod version ^1.0.4. When I execute a function in my StateNotifier the state in the function changes, but in the UI part, it does not change. The state always stays ListInit. But I do not know why. I printed the state runtimetype and in the function, it changes but not in the UI part. I have used the same structure in another project with riverpod version ^0.14.0+3), it works there (I added the new changes of version ^1.0.4 to my code). I don't know what I miss. I went over the code several times but found nothing. And I also don't get an error. Do I miss something?

import 'package:finanz_app/models/konten.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hive_flutter/hive_flutter.dart';

abstract class ListState {
  const ListState();
}

class ListInit extends ListState {
  const ListInit();
}

class ListLoding extends ListState {
  const ListLoding();
}

class ListLoded extends ListState {
  final List<Konten> konten;
  const ListLoded(this.konten);
}

class ListError extends ListState {
  final String errorMessage;
  const ListError(this.errorMessage);
}

final listNotifierProvider = StateNotifierProvider((ref) {
  return ListStateNotifier();
});

class ListStateNotifier extends StateNotifier<ListState> {
  ListStateNotifier() : super(const ListInit());

  Future<void> getFromDB() async {
    state = const ListLoding();

    final box = Hive.box('konten');

    final List<Konten> kontenData = box.keys.map((key) {
      final Konten value = box.get(key);
      return value;
    }).toList();

    print(kontenData.length);
    state = ListLoded(kontenData);

    print(state.runtimeType);
  }

  Future<void> add(Konten konto) async {
    final box = Hive.box('konten');

    box.add(konto);

    print(state.runtimeType);

    getFromDB();
  }

  Future<void> deletedAll() async {
    final box = Hive.box('konten');

    for (int key in box.keys) {
      box.delete(key);
    }

    getFromDB();
  }
}


...
body:
  Consumer(
    builder: (context, ref, child) {
      final state = ref.watch(listNotifierProvider);

      print(state.runtimeType);

      if (state is ListInit) {
        return const Text(
          "InitState",
        );
      } else if (state is ListLoding) {
        return const CircularProgressIndicator();
      } else if (state is ListLoded) {
        return Text(state.konten.length.toString());

      } else if (state is ListError) {
        return Text("ERROR: ${state.errorMessage}");
      } else {
        return const Text("Unknown ERROR");
      }
    },
  ),
...

@override
  Widget build(BuildContext context) {
    return ProviderScope(
      child: MaterialApp(
        ...
        initialRoute: '/',
        routes: {
          "/": (context) => const HomePage(),
        },
      ),
    );
  }
Berkkan
  • 97
  • 1
  • 4
  • 8
  • It's possible the state is not changing. You need to ensure that the new state is a new object, not a mutated version of the existing state. – Randal Schwartz Sep 25 '22 at 19:19

1 Answers1

0

The problem is that we need to make sure that the new state is a new object. The state also must not be a mutable object.

You may be try to features like "union types"/"sealed classes"/pattern matching. Using the freezed library, it's easy to do this:

@freezed
class ListState with _$ListState {
  const factory ListState.init() = Init;
  const factory ListState.loading() = Loading;
  const factory ListState.loaded(List<Konten> konten) = Loaded;
  const factory ListState.error(String? errorMessage) = Error;
}

and inside build method it looks like this:

final ListState state = ref.watch(listNotifierProvider);

print(
  state.when(
    init: () => 'init',
    loading: () => 'loading',
    loaded: (List<Konten> konten) => 'loaded',
    error: (String? errorMessage) => 'Error: $message',
  ),
);
Ruble
  • 2,589
  • 3
  • 6
  • 29