rrousselGit / riverpod

A reactive caching and data-binding framework. Riverpod makes working with asynchronous code a breeze.
https://riverpod.dev
MIT License
6.17k stars 943 forks source link

How to properly use riverpod with service pattern #277

Closed yvz5 closed 3 years ago

yvz5 commented 3 years ago

Hi there, I have a use case where I think would be a great idea to use a service pattern but I struggle to understand how would I go about using it with riverpod.

Imagine the following scenario: The app has a feature which allows users to upload files and the app would then read those files. In an OOP world I would mix repository pattern with service pattern. XmlRepository, JsonRepository, CsvRepository and a service to decide which repository to use. But my problem is, there is no way to use Future<List<Obj>> on the UI side if that method is inside a normal Provider.

final xmlRepositoryProvider = Provider(
  (ref) => XmlRepository(),
);

class XmlRepository implements IFileReader {
  Future<List<Obj>> getAll() async {
      ... 
  }
}

Imagine other repositories being done this way. And then there is the service:

final fileServiceProvider = Provider(
  (ref) => FileService(ref),
);

class FileService implements IFileService {
  final ProviderReference providerReference;

  FileService (this.providerReference);

  Future<List<Obj>> getAll(FileType fileType) async {
      if (fileType == "XML") {
          var xmlRepository = this.providerReference.read(xmlRepositoryProvider);
          return await xmlRepository.getAll();
      }
      ..
  }
}

But there is no way to use the getAll method with AsyncValue as you can with FutureProvider. So how do you do this in idiomatic riverpod in UI without having to declare an extra FutureProvider ?

feronetick commented 3 years ago

I use like this

final fileServiceProvider = Provider((ref) {
  return FileService(
    xmlRepository: ref.watch(xmlRepositoryProvider),
  );
});

class FileService implements IFileService {
  FileService({
    @required this.xmlRepository,
  }) : assert(xmlRepository != null);

  @protected
  final XmlRepository xmlRepository;

  /// ....
}
yvz5 commented 3 years ago

Why do you watch and not read ?

how do you handle the UI side ?

final listProvider = FutureProvider<String>((ref) async {
  return "txt";
});

and in UI you can do the following:


  Widget build(BuildContext context) {
      return Center(
          child: Consumer(
            builder: (context, watch, child) {
              final asyncValue = watch(listProvider);
              return asyncValue.map(
                loading: (_) => ..
                error: (_) => ..
                data: (_) => ..
              );
           }
       }

but How would I do this for a method inside a Service ?

feronetick commented 3 years ago

Why do you watch and not read ?

We should not use read inside provider body. https://riverpod.dev/docs/concepts/reading#contextreadmyprovider

but How would I do this for a method inside a Service ?

You can just create future provider for method result

final regionsProvider = FutureProvider<List<Region>>((ref) {
  return ref.watch(regionsServiceProvider).fetch();
});

class MyWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    return watch(regionsProvider).when(
      data: (regions) {
        return ListView(
          children: [
            for (final region in regions) 
              Text('${region.name}')
          ],
        );        
      },
      loading: () {
        return Text('loading');
      },
      error: (error, stack) {
        return Text('error');
      }
    );
  }
}
yvz5 commented 3 years ago

Would you create a FutureProvider for each Future<List> inside a repository ? This doesnt sound like a great design, does it ?

TimWhiting commented 3 years ago

If you are open to using riverpod with flutter_hooks you could use the useFuture hook which will give you an async snapshot without having to create a future provider for each api call. https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/useFuture.html

Or you can use a FutureBuilder from flutter (but this will involve more nesting, and a FutureProvider would look cleaner in my opinion).

chaturadilan commented 1 year ago

I developed the below library which automatically generate future providers from the repository methods. If anything you need to change in the library, I'm open for discussion in the issues section

https://pub.dev/packages/riverpod_repo