Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

how to use RepositoryProvider #1204

Closed
Karatla opened this issue May 23, 2020 · 33 comments
Closed

how to use RepositoryProvider #1204

Karatla opened this issue May 23, 2020 · 33 comments
Assignees
Labels
question Further information is requested

Comments

@Karatla
Copy link

Karatla commented May 23, 2020

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
Copy link
Contributor

narcodico commented May 23, 2020

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 felangel added question Further information is requested waiting for response Waiting for follow up labels May 23, 2020
@felangel
Copy link
Owner

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 👍

@felangel felangel removed the waiting for response Waiting for follow up label May 27, 2020
@aliftaufik
Copy link

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
Copy link

@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
Copy link
Owner

You can refer to

value: _weatherRepository,
for an example 👍

@xuanswe
Copy link

xuanswe commented May 16, 2021

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
Copy link

drogbut commented May 24, 2021

@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
Copy link

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

@aliftaufik
Copy link

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
Copy link
Collaborator

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
Copy link

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
Copy link

fmdogan commented Jan 10, 2022

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
Copy link

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
Copy link

@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
Copy link

@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
Copy link

fmdogan commented Jan 14, 2022

@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
Copy link

otto-dev commented Jan 27, 2022

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
Copy link

@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
Copy link
Contributor

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
Copy link

adel9504 commented Nov 8, 2022

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

@felangel
Copy link
Owner

felangel commented Nov 8, 2022

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
Copy link

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

@narcodico
Copy link
Contributor

@furkansarihan repositories should not depend on each other.

@otto-dev
Copy link

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
Copy link
Contributor

@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
Copy link

clragon commented Feb 19, 2023

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:

  • a function to create a value T (create)
  • a function that returns a List<Object?> (select)
  • a function that builds a child widget with value T

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
Copy link

clragon commented May 9, 2023

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
Copy link

stevemayne commented Jun 3, 2023

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
Copy link

clragon commented Jun 3, 2023

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
Copy link

reke592 commented Jul 2, 2023

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.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
Copy link

stevemayne commented Jul 2, 2023 via email

@reke592
Copy link

reke592 commented Jul 3, 2023

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
Copy link

becjit commented Sep 5, 2023

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests