-
-
Notifications
You must be signed in to change notification settings - Fork 3.4k
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
Comments
Hi @Karatla 👋 Please have a look at repository provider documentation for a quick intro.
As for your error please share a github gist or repo and I'll have a look. 👍 |
As always, thanks @RollyPeres for taking the time to answer! |
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? |
@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 |
You can refer to
|
If I replace Honestly, I still cannot understand the benefits of I mean, I can still follow the architecture overview using the original |
@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. |
it seems RepositoryProvider is just a dependency shared by multi Blocs for a BlocProvider |
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. |
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. |
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. |
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?) |
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. |
@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. |
@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 Blocs are notified via events. Communication between blocs is another subject. |
The |
@felangel @otto-dev @narcodico @fmdogan |
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).
|
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 |
@felangel What about accessing to repository providers from each other in a multi repository provider? |
@furkansarihan repositories should not depend on each other. |
You can, through the
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. |
@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! |
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. I had hoped that Bloc would solve this problem, but it appears that it is not talked about at all. Could someone clear up my confusion here? |
In the meanwhile, I have written an entire package to fix this problem: 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? |
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 |
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. 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. |
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.
A Repository is like a mediator between the bloc and data sources.
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
// 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));
}
}
} |
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<Object> get props => [];
}
class LoadTodos extends TodosEvent {}
// todos_state.dartpart 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 <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<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));
}
}
}
—
Reply to this email directly, view it on GitHub
<#1204 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AAICIQUUEJ2RBO5S2PTA4G3XOE3PPANCNFSM4NIMS5TQ>
.
You are receiving this because you were mentioned.Message ID:
***@***.***>
|
thanks @stevemayne I will practice this one. |
@stevemayne I am trying to solve the exact problem as yours i.e.
How exactly you change the repository based on the user? Is it based on another bloc. In short, is the flow something like this: |
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
The text was updated successfully, but these errors were encountered: