Search code examples
flutterdartproviderriverpodstate-management

Error Migrating from Provider to Riverpod: 'ChangeNotifierProvider' isn't a function


I'm in the process of migrating my Flutter app from using the Provider package to Riverpod for state management. However, I've encountered a stumbling block with the following error message:

'ChangeNotifierProvider' isn't a function. Try correcting the name to match an existing function, or define a method or function named 'ChangeNotifierProvider'

This error appears during compilation after attempting to replace Provider with Riverpod equivalents in my main app file.

My aim is to leverage Riverpod for its enhanced features and scalability. I'm particularly interested in using its ChangeNotifierProvider for managing app state.

What I've Tried:

  • Checked the Riverpod documentation for migration tips.
  • Searched for similar error messages but found none that addressed my specific transition from Provider to Riverpod.

Code Example:

void main() async {
  await Hive.initFlutter();
  await Hive.openBox("Habit_Database");

  runApp(MultiProvider(providers: [
    ChangeNotifierProvider(
      create: (context) => UserProvider(),
    ),
  ], child: const MyApp()));
}

Question: How can I correctly migrate ChangeNotifierProvider from Provider to Riverpod, resolving the error mentioned above? Any specific examples or documentation references would be greatly appreciated.

Thank you in advance for your help!


Solution

  • I'm working on this right now for a client. I have chosen to maintain the existing provider infrastructure while slowly migrating one provider at a time to riverpod.

    To make this work, I first constructed a renaming shim for all of the exported provider functions. It imports provider, and establishes typedefs to alias those names to universally end in "X", including the extension on BlockContext which becomes .readX and .selectX. I tested this by not having the "X" initially, then renaming each symbol in VSC one at a time, which surprisingly worked well. The renaming shim looks something like:

    import 'package:provider/provider.dart' as provider;
    
    typedef ProviderX<T> = provider.Provider<T>;
    typedef MultiProviderX = provider.MultiProvider;
    typedef ChangeNotifierProviderX<T extends ChangeNotifier> = provider.ChangeNotifierProvider<T>;
    

    which continues for about 100 lines. The tricky ones are the extensions:

    extension ReadContext on BuildContext {
      T readX<T>() => provider.ReadContext(this).read<T>();
    }
    
    extension SelectContext on BuildContext {
      R selectX<T, R>(R Function(T value) selector) => provider.SelectContext(this).select(selector);
    }
    

    Admittedly, once I started the pattern, Github copilot eagerly offered me line after line, and was wrong for only a few things initially.

    Next, I added the RiverPod ProviderScope to my runApp, and selected a particular provider to migrate. I created the equivalent in RiverPod, nicely namespaced because "FooProvider" became "fooProvider", and then located all references to that class in .readX or ConsumerX access. I inserted the equivalent with Consumer blocks or ConsumerWidget widgets, giving me a ref to use with ref.read or ref.watch as appropriate.

    It's not trivial. But once you get over "the great rename" hurdle, the rest is just a rather mechanical translation, and can be done incrementally.