felangel / bloc

A predictable state management library that helps implement the BLoC design pattern
https://bloclibrary.dev
MIT License
11.66k stars 3.38k forks source link

how to use RepositoryProvider #1204

Closed Karatla closed 4 years ago

Karatla commented 4 years ago

hi ,thanks for this plugin i am new to use this i do not know how to use RepositoryProvider. could you give me a example of using RepositoryProvider? i do not know whats difference between RepositoryProvider and blocprovider . and i have anthoer problem which is when i use bloc.close()in void dipose() in stateFullwidget . when i close this widget it give me this error: type 'Future' is not a subtype of type 'Future'

could you please help me ? thank you so much

narcodico commented 4 years ago

Hi @Karatla 👋

Please have a look at repository provider documentation for a quick intro.

RepositoryProvider allows you to pass an instance of a repository to a sub-tree from where you can access it via context.read<YourRepositoryType>(). A typical use case is to inject a repository into a bloc:

RepositoryProvider(
          create: (context) => UserRepository(),
          child: BlocProvider(
            create: (context) => AuthBloc(
              userRepository: context.read<UserRepository>(),
            ),
            child: ...,
          ),
        )

RepositoryProvider is a specialized widget used to provide a repository while the BlocProvider is a specialized widget used to provide a bloc. As a best practice repositories are injected into blocs and act as an abstraction layer between your data sources and your business logic component(bloc). For more details please have a look at bloc's architecture overview.

As for your error please share a github gist or repo and I'll have a look. 👍

felangel commented 4 years ago

As always, thanks @RollyPeres for taking the time to answer! @Karatla closing this for now but feel free to comment with additional questions and I'm happy to continue the conversation 👍

aliftaufik commented 3 years ago

As far as I am using flutter bloc, I never find a useful way of using the RepositoryProvider, because I instantiate the Repository directly in the bloc constructor. And because I use reverse dependency to abstract the Repository in bloc logic, that means the Repository will have all the required implementation for the specific bloc, and thus makes it a 1-to-1 dependency. So, a specific Repository is used for specific Bloc. That's why I have no idea when and why to use RepositoryProvider. Could you give some practical examples of it?

guopeng1994 commented 3 years ago

@aliftaufik yeah, me too. in my project, i never used RepositoryProvider, and i init state data just in Bloc initialState, and change state data in mapEventToState function by return a copied state data object. so really don't know when and why we need RepositoryProvider. in my case ,Bloc is enough

felangel commented 3 years ago

You can refer to https://github.com/felangel/bloc/blob/29da6542c40676a9c3f7d443f5673945d2c2781d/examples/flutter_weather/lib/app.dart#L18 for an example 👍

xuanswe commented 3 years ago

If I replace RepositoryProvider with just Provider from provider package, everything still works without any differences.

Honestly, I still cannot understand the benefits of RepositoryProvider over Provider.

I mean, I can still follow the architecture overview using the original Provider from provider package and don't need RepositoryProvider at all. 😕

drogbut commented 3 years ago

@nguyenxndaidev After several weeks on the subject I think I have understood a bit the difference in the use of the RepositoryProvider. In case you have several repositories to manage (e.g. UserRepository (FirebaseAuth) and ArticleRepository(firestore)), it is more efficient to initialize each Repository in its block. The best approach here would be to initialize each one in its builder.

Now in the case where the application has only one repository, we can wrap the block in the repository. Except that again I wonder what the point of MultiblocRepository is?

So far, the difference remains very abstract and vague.

jinjilynn commented 2 years ago

it seems RepositoryProvider is just a dependency shared by multi Blocs for a BlocProvider

aliftaufik commented 2 years ago

So basically RepositoryProvider will be used only when the repository is required by multiple Blocs, else you just instantiate the repository directly when instantiating the Bloc. But often my repository is just a bunch of methods handling API calls and data mapping, without any state/field being kept inside that class. So instantiating the same repository in different Bloc instantiations won't do any harm and render RepositoryProvider useless.

Probably another way to make use of RepositoryProvider is if we move the state data from Bloc to the repository. So Bloc state will only be used for UI data (flagging etc.) and showing only parts of data required for the UI. Data change should all be handled by the repository as that is the source of truth now. But then multiple Blocs sharing the same repository instance should know when other Bloc triggers data change to get the update, hence making repository works like Bloc (listen to events, update the state, notify listeners, etc.).

With that being said, I still can't find any case where using RepositoryProvider outweigh instantiating repository directly in any Bloc instantiations that requires it (dart claims that it can easily manage a large number of class instantiations and destructions easily as far as I remember). I hope @felangel can give an example where RepositoryProvider is really a better approach.

marcossevilla commented 2 years ago

hi @aliftaufik! I did this example months ago and use different repository methods for 2 different blocs that are like sub-features and use the same repository.

You can check it here.

Hopefully, that clarifies the possible use cases.

a-hamouda commented 2 years ago

From a Multi-Layered architecture perspective, a bloc which lives in the domain layer shouldn't have any dependency on any other layer. This rule is broken when instantiation of a repository happens directly inside the bloc since it's more likely that the repository depends on other layers.

fmdogan commented 2 years ago

RepositoryProvider sounds more like a data store for me. I used to store some data like infos of the logged-user in a global class or global variable which i can access from anywhere through the app like data providers, data repositories, blocs and maybe even presentation.. (feels like it ain't the professional way, is it?) Repository provider may keep these datas available to all blocs.. then I need to pass the data to the repositories and data providers from bloc when those datas are needed..

aliftaufik commented 2 years ago

hi @aliftaufik! I did this example months ago and use different repository methods for 2 different blocs that are like sub-features and use the same repository.

You can check it here.

Hopefully, that clarifies the possible use cases.

If WeatherRepository only contains methods for handling weather data requests, just as how I used it, I can refactor your code and just instantiate WeatherRepository on each Bloc creations. If both Blocs don't need to hang on the same Repository instance, I believe it doesn't hurt the app performance anyway.

aliftaufik commented 2 years ago

@YusufJo The repository is not instantiated inside the Bloc, but instantiated in Bloc creation, when passing it as params. In my case, my Bloc will have an interface as its constructor parameter, and the Repository will implement it.

aliftaufik commented 2 years ago

@fmdogan if you put your data in Repository, how to sync it between Blocs? I mean, if one Bloc changes some values, how to notify other Blocs that some values in the Repository have changed?

fmdogan commented 2 years ago

@fmdogan if you put your data in Repository, how to sync it between Blocs? I mean, if one Bloc changes some values, how to notify other Blocs that some values in the Repository have changed?

@aliftaufik It is repositoryProvider. It doesn't have to notify blocs about changes, I assume. Blocs will notice the change when they fetch the data from repositoryProvider for an event. This applies for my use case since what I only expect a repositoryProvider to do is providing me the data.

Blocs are notified via events. Communication between blocs is another subject.

otto-dev commented 2 years ago

RepositoryProvider helps to properly divide the responsibilities in your codebase. Any complex repository with authorization, network connections etc. should only be instantiated once in the application.

The Bloc should not know how to instantiate and configure the Repository. The Widget should not know which Repositories the Bloc is going to use. RepositoryProvider hides these details by making repositories accessible through the BuildContext after it has been created in an appropriate location in the codebase. The repository can then be accessed by any Bloc, while the widget does not have to provide more than the context. Even if internal dependencies of the Bloc change in the future, eg. the bloc uses a second repository that was not there in the first place: The widget does not know this, and the bloc does not have to know how to instantiate the new repository.

magician20 commented 2 years ago

@felangel @otto-dev @narcodico @fmdogan What about if we using injectable pub to handle injecting the repository to the bloc and add @injectable to the bloc is that equal to RepositoryProvider or not and why ?

narcodico commented 2 years ago

Hi @magician20 👋

injectable is a way of generating DI code which will basically instantiate your objects with the proper dependencies, which you can then access through get_it, from anywhere in your app basically(which sounds cool but it really isn't).

RepositoryProvider is a widget which you can make use of to provide an instance to a sub-tree of widgets, which you can then access through the widget tree ONLY(which is better than from anywhere simply because it allows for dependency scoping).

adel9504 commented 1 year ago

Where to put the logic to decide if I provide local or remote repository?

felangel commented 1 year ago

Where to put the logic to decide if I provide local or remote repository?

I recommend having the repository make that decision. For example, you can have a UserRepository which either pulls user data from a backend (api client) or from a local db.

furkansarihan commented 1 year ago

@felangel What about accessing to repository providers from each other in a multi repository provider?

narcodico commented 1 year ago

@furkansarihan repositories should not depend on each other.

otto-dev commented 1 year ago

What about accessing to repository providers from each other in a multi repository provider?

You can, through the context provided by in create by each of the child RepositoryProviders, as long as the repository you are trying to access was listed as a previous child in the providers. So the order of the providers in the MultiRepositoryProvider matters.

repositories should not depend on each other.

I would disagree, of course they can if it makes sense to do so in respect to the responsibilities of the repositories. These kind of rules sound smart but don't help.

narcodico commented 1 year ago

@otto-dev that is not a rule, it's a simple guideline. Of course you can have all your repositories depend on each other if you think that's a good approach. Obviously you can route your data through the blocs and achieve a better architecture overall. Sorry if that sounds smart, I really wasn't trying!

clragon commented 1 year ago

The problem that nobody seems to talk about is that this is a Provider-Provider dependency.

I have this problem in my own app, where I do not use Bloc, but still have Provider-Provider dependencies, in structures that are similar to Repository-Bloc relationships.

The problem with the Provider-Provider dependency, while not obvious at first, is that if the Bloc depends on the Repository as a parameter, then the Repository is read exactly once. If I were to now switch out my Repository at the root of my app for one reason or another and rebuild my app, nothing would happen. My Bloc would still reference the old Repository as there is no mechanism to inform my Bloc of this change or to recreate it.

I am of the opinion that in such situations, the Bloc should be recreated entirely, as its primary (?) data source has changed and therefore all data inside of the Bloc has become invalid. This behaviour can be achieved, but there is no inbuilt way of doing so with Provider.

I have solved this in my own app by creating a StatefulWidget that takes 3 arguments:

This StatefulWidget will then create and hold the value T and pass it to its child. If the List<Object?> returned by the select function changes (DeepEquality), the create function is called again to recreate the value T and the builder function is then passed the new value. This is obviously taking shortcuts and is not exactly type safe, but its just about good enough for my personal app or an internal implementation.

This StatefulWidget is then wrapped in some helper classes that make it easy to use Provider in create and select. With this, all my "Blocs" or just "Controllers" are recreated when their dependencies change. These dependencies can be arbitrary values, but are primary sources of data, like a http client, which has a specific final configuration and when changed, all data becomes invalid. Here, things could be made type safe again (however, in my app, I chose not to, as arbitrary dependencies that trigger a recreate of a Controller are very useful).

I had hoped that Bloc would solve this problem, but it appears that it is not talked about at all. Maybe I am simply not understanding a concept correctly, but this problem bothers me and I came accross this issue in search for an answer.

Could someone clear up my confusion here?

clragon commented 1 year ago

In the meanwhile, I have written an entire package to fix this problem: https://pub.dev/packages/flutter_sub_provider is basically a shortcut for a StatefulWidget that takes in dependencies as keys and recreates Providers when those keys change.

But I would still be very interested in how exactly this problem is meant to be solved in bloc, since I am considering migrating my app to it. Would you care to comment on this, @felangel?

stevemayne commented 1 year ago

The problem with the Provider-Provider dependency, while not obvious at first, is that if the Bloc depends on the Repository as a parameter, then the Repository is read exactly once. If I were to now switch out my Repository at the root of my app for one reason or another and rebuild my app, nothing would happen. My Bloc would still reference the old Repository as there is no mechanism to inform my Bloc of this change or to recreate it.

I am of the opinion that in such situations, the Bloc should be recreated entirely, as its primary (?) data source has changed and therefore all data inside of the Bloc has become invalid. This behaviour can be achieved, but there is no inbuilt way of doing so with Provider.

I have a similar problem where I need to recreate repositories if the user context changes. The local database in the app is specific to the logged-in user. If I switch user contexts, many of my repositories need to be recreated.

To solve this, I have a widget tree that introduces user-context-specific RepositoryProvider widgets (and other Blocs that depend on them) underneath the top-level block builder for my AuthenticationBloc. These RepositoryProvider widgets are recreated when the AuthenticationBloc changes user-context, providing a whole new repository context to the rest of the app.

Does that sound like it would meet your needs? @clragon And is there a better way to do this?

clragon commented 1 year ago

To solve this, I have a widget tree that introduces user-context-specific RepositoryProvider widgets (and other Blocs that depend on them) underneath the top-level block builder for my AuthenticationBloc. These RepositoryProvider widgets are recreated when the AuthenticationBloc changes user-context, providing a whole new repository context to the rest of the app.

That's sounds a bit like what I'm doing but less reusable and centralised.

A SubProvider creates a Object based on dependencies. With a SubProvider you can depend on your database, then in the SubProvider create a new Repository. It doesn't have to contain the accompanying blocs because those themselves can be created in SubProviders which depend on the result of this one.

That way, the depth at which this kind of dependency is resolved is irrelevant and we can freely exchange our dependencies at the top without having to specifically look out for this down below in the widget tree.

In that way, your solution does meet my needs, but not better than inbuilt support for this kind of feature or a third party package like flutter_sub_provider.

I don't want to do this by hand and for each Bloc or Repository. With something like SubProvider, all of this is automatic and guaranteed.

It's a bit similar to react hooks, where this kind of "recreate my object if the dependency changes" is very prevelant. it seems this concept is just largely ignored in the flutter community, which is a shame because it's definitely there and can be utilized.

reke592 commented 1 year ago

It's my first time trying the _flutterbloc kindly correct me if I'm wrong.

It seems like we can skip the RepositoryProvider when we don't need the context to access the object instance.

eg. when using GetIt as service locator

A Repository is like a mediator between the bloc and data sources.

eg. data sources: web API, sqflite, SharedPreference repositories: SessionRepository, TodosRepository
purpose: sessRepo.attemptLogin(...), sessRepo.accessToken, todosRepo.getList(...), todosRepo.add(...)

The repository is the one in-charge for queueing, sending request and cache loading.

Bloc pattern follows event driven architecture, if we need something, we listen to events. I download the bloc extension on VsCode at first glance it seems like the naming convention is a little bit misleading because from what I knew events should be in past tense. It turns out that the events are Action events and the state is a result of certain event.

blocs/
'-- todos_bloc.dart
'-- todos_event.dart
'-- todos_state.dart
// todos_event.dart
part of 'todos_bloc.dart';

abstract class TodosEvent extends Equatable {
  const TodosEvent();

  @override
  List<Object> get props => [];
}

class LoadTodos extends TodosEvent {}
// todos_state.dart
part of 'todos_bloc.dart';

abstract class TodosState extends Equatable {
  const TodosState();

  @override
  List<Object> get props => [];
}

class TodosInitial extends TodosState {}

class TodosLoaded extends TodosState {
  final List<Todo> records;
  const TodosLoaded({
    this.records = const <Todo>[],
  });
  @override
  List<Object> get props => [records];
}

class TodosLoadingFailed extends TodosState {
  final dynamic error;
  final StackTrace? stackTrace;
  const TodosLoadingFailed({
    required this.error,
    this.stackTrace,
  });
  @override
  List<Object> get props => [error];
}

in case want the TodosBloc to listen on SessionBloc, like from the concern of @stevemayne, let say we have this SwitchUser session state, we need to subscribe the TodosBloc in SessionBloc event stream.

// todos_bloc.dart
class TodosBloc extends Bloc<TodosEvent, TodosState> {
  final TodosApi _api;
  final SessionBloc _sessionBloc;
  late StreamSubscription _sessionSubscription;

  TodosBloc({
    required TodosApi api,
    required SessionBloc sessionBloc,
  })  : _api = api,
        _sessionBloc = sessionBloc,
        super(TodosInitial()) {
    on<LoadTodos>(_onLoadTodos);
    _initSubscription();
  }

  void _initSubscription() {
    _sessionSubscription = _sessionBloc.stream.listen((event) {
      // trigger this bloc _onLoadTodos
      if (event is SwitchUser) {
        add(LoadTodos());
      }
    });
  }

  @override
  Future<void> close() async {
    await _sessionSubscription.cancel();
    return super.close();
  }

  void _onLoadTodos(LoadTodos event, Emitter<TodosState> emit) async {
    try {
      final results = await _api.getList();
      emit(TodosLoaded(records: results));
    } catch (error, stackTrace) {
      emit(TodosLoadingFailed(error: error, stackTrace: stackTrace));
    }
  }
}
stevemayne commented 1 year ago

You are correct. If you use Getit (or similar) you don't need to use the repository pattern.

Blocs should not listen to other Blocs directly - they should not be tightly coupled. Blocs can listen to repos, and repos can listen to repos (using streams or callbacks or whatever), or you can use a BlocListener to respond to the state change of one Bloc and fire an event at another.

On Sun, 2 Jul 2023 at 10:12, Erric Rapsing @.***> wrote:

It's my first time trying the flutter_bloc kindly correct me if I'm wrong.

It seems like we can skip the RepositoryProvider when we don't need the context to access the object instance.

eg. when using GetIt as service locator

A Repository is like a mediator between the bloc and data sources.

eg. data sources: web API, sqflite, SharedPreference repositories: SessionRepository, TodosRepository purpose: sessRepo.attemptLogin(...), sessRepo.accessToken, todosRepo.getList(...), todosRepo.add(...)

The repository is the one in-charge for queueing, sending request and cache loading.

Bloc pattern follows event driven architecture, if we need something, we listen to events. I download the bloc extension on VsCode at first glance it seems like the naming convention is a little bit misleading because from what I knew events should be in past tense. It turns out that the events are Action events and the state is a result of certain event.

blocs/ '-- todos_bloc.dart '-- todos_event.dart '-- todos_state.dart

// todos_event.dartpart of 'todos_bloc.dart'; abstract class TodosEvent extends Equatable { const TodosEvent();

@override List get props => []; } class LoadTodos extends TodosEvent {}

// todos_state.dartpart of 'todos_bloc.dart'; abstract class TodosState extends Equatable { const TodosState();

@override List get props => []; } class TodosInitial extends TodosState {} class TodosLoaded extends TodosState { final List records; const TodosLoaded({ this.records = const [], }); @override List get props => [records]; } class TodosLoadingFailed extends TodosState { final dynamic error; final StackTrace? stackTrace; const TodosLoadingFailed({ required this.error, this.stackTrace, }); @override List get props => [error]; }

in case want the TodosBloc to listen on SessionBloc, like from the concern of @stevemayne https://github.com/stevemayne, let say we have this SwitchUser session state, we need to subscribe the TodosBloc in SessionBloc event stream.

// todos_bloc.dartclass TodosBloc extends Bloc<TodosEvent, TodosState> { final TodosApi _api; final SessionBloc _sessionBloc; late StreamSubscription _sessionSubscription;

TodosBloc({ required TodosApi api, required SessionBloc sessionBloc, }) : _api = api, _sessionBloc = sessionBloc, super(TodosInitial()) { on(_onLoadTodos); _initSubscription(); }

void _initSubscription() { _sessionSubscription = _sessionBloc.stream.listen((event) { // trigger this bloc _onLoadTodos if (event is SwitchUser) { add(LoadTodos()); } }); }

@override Future close() async { await _sessionSubscription.cancel(); return super.close(); }

void _onLoadTodos(LoadTodos event, Emitter emit) async { try { final results = await _api.getList(); emit(TodosLoaded(records: results)); } catch (error, stackTrace) { emit(TodosLoadingFailed(error: error, stackTrace: stackTrace)); } } }

— Reply to this email directly, view it on GitHub https://github.com/felangel/bloc/issues/1204#issuecomment-1616503196, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAICIQUUEJ2RBO5S2PTA4G3XOE3PPANCNFSM4NIMS5TQ . You are receiving this because you were mentioned.Message ID: @.***>

reke592 commented 1 year ago

Blocs should not listen to other Blocs directly - they should not be tightly coupled. Blocs can listen to repos, and repos can listen to repos (using streams or callbacks or whatever), or you can use a BlocListener to respond to the state change of one Bloc and fire an event at another.

thanks @stevemayne I will practice this one.

becjit commented 11 months ago

@stevemayne I am trying to solve the exact problem as yours i.e.

I have a similar problem where I need to recreate repositories if the user context changes. The local database in the app is specific to the logged-in user. If I switch user contexts, many of my repositories need to be recreated.

To solve this, I have a widget tree that introduces user-context-specific RepositoryProvider widgets (and other Blocs that depend on them) underneath the top-level block builder for my AuthenticationBloc. These RepositoryProvider widgets are recreated when the AuthenticationBloc changes user-context, providing a whole new repository context to the rest of the app.

How exactly you change the repository based on the user? Is it based on another bloc. In short, is the flow something like this: UserBloc--> Select the repository-> Inject the repository into other blocs? I am using GetIt service locator. Any idea how could I do it using GetIt. It would be super helpful if you happen to have any pseudocode/ code