flutterblocflutter-blocflutter-workmanager

Flutter BLoC et workmanager : how to make unique repositories initialisation pattern?


I use the BLoC, and workmanager libraries.

Actually, the code runs fine. But, looks like smell code.

My problem : I have to initialise all my repositories for the MultiRepositoryProvider which wraps my MainApp() widget. At this point, basic stuff.

void main() async {
 
  final RepositoryOne repoOne = RepositoryOne();
  final RepositoryTwo repoTwo = RepositoryTwo();

  runApp(
    MultiRepositoryProvider(
      providers: [
        RepositoryProvider(
          create: (context) => repoOne,
        ),
        RepositoryProvider(
          create: (context) => repoTwo,
        ),
      ],
      child: const MyApp(),
    ),
  );
}

But, when I declare a job for my workmanager, I have to reinitialise some of these repositories.

@pragma('vm:entry-point')
void callbackDispatcher() {
  Workmanager().executeTask((task, inputData) async {
    switch (task) {
      case 'synchronize':
          final RepositoryOne repoOne = RepositoryOne();
          final RepositoryTwo repoTwo = RepositoryTwo();
        
            List<int> result = await repoOne.synchronize();
            if (result.isNotEmpty) {
             await repoTwo.doThings(result);
            }
        break;
    }
    return Future.value(true);
  });
}

I tried several things to export in the same place the repository initialisation, but no success...

Does somebody have an idea to not duplicate repositories initialisation?

Solution chosen

I get rid of a service locator library to do this.

What I've done :


late RepositoryOne repoOne;
late RepositoryTwo repoTwo;
late RepositoryThree repoThree;
late RepositoryFour repoFour;

void initDependenciesStuff() {
  repoOne = RepositoryOne();
  repoTwo = RepositoryTwo();
}

void initAnotherDependenciesStuff() {
  repoThree = RepositoryThree();
  repoFour = RepositoryFour();
}

void main() {
  initDependenciesStuff();
  initAnotherDependenciesStuff();

  runApp(
    MultiRepositoryProvider(
      providers: [
        RepositoryProvider(
          create: (context) => repoOne,
        ),
        RepositoryProvider(
          create: (context) => repoTwo,
        ),
        RepositoryProvider(
          create: (context) => repoThree,
        ),
        RepositoryProvider(
          create: (context) => repoFour,
        ),

      ],
      child: const MyApp(),
    ),
  );
}


@pragma('vm:entry-point')
void callbackDispatcher() {
  Workmanager().executeTask((task, inputData) async {
    switch (task) {
      case 'firstThing':
        initDependenciesStuff();

        List<int> stuff = repoOne.fetchStuff();
        await repoTwo.editStuffDatabase(stuff);
       
        return Future.value(true);
      case 'secondThing':
        initAnotherDependenciesStuff();

        List<int> updatedStuff = repoFour.updateStuff();
        await repoThree.editStuffDatabase(updatedStuff);

        return Future.value(true);
      case 'thirdThing':
        initDependenciesStuff();
        initAnotherDependenciesStuff();

        // Another stuffs to do

        return Future.value(true);
    }

    return Future.value(true);
  });
}

Solution

  • from the documentation callbackDispatcher is the top level function.

    so the best way to provide the same class instance in different app places is using dependency injection or service locator e.g. injectable or get_it.

    for get_it it will something like this. some where in main:

    import 'package:app.taletwist/injection_container.dart' as di;
    
      await di.init();
      runApp(..);
    

    then in injection_container.dart:

    final sl = GetIt.instance;
    
    Future<void> init() async {
      sl
        ..registerSingleton(RepositoryOne())
        ..registerSingleton(RepositoryTwo())
    }
    

    and then wherenever you need it:

      runApp(
        MultiRepositoryProvider(
          providers: [
            RepositoryProvider(
              create: (context) => sl<RepositoryOne>(),
            ),
            RepositoryProvider(
              create: (context) => sl<RepositoryTwo>(),
            ),
          ],
          child: const MyApp(),
        ),
      );
    

    or

    @pragma('vm:entry-point')
    void callbackDispatcher() {
      Workmanager().executeTask((task, inputData) async {
        switch (task) {
          case 'synchronize':
              final repoOne = sl<RepositoryOne>();
              final repoTwo = sl<RepositoryTwo>();
            
                List<int> result = await repoOne.synchronize();
                if (result.isNotEmpty) {
                 await repoTwo.doThings(result);
                }
            break;
        }
        return Future.value(true);
      });
    }