Bloc: [Question] Help with App Architecture

Created on 7 Jan 2020  路  10Comments  路  Source: felangel/bloc

I have to develop a simple app with Flutter and I am trying for the first time the bloc pattern. I am using flutter_bloc package but I have two main problems and got stuck:

  1. All the screens in the app have blocs that depends on repos to make requests to a server, and for each request I need a token. I obtain the token at the authentication_bloc (login), but I can't share that token to all the blocs unless I make them dependent on auth_bloc (the bloc who has the user model storing the token). For a simple app, maybe it works but if other bloc has more bloc dependencies besides the auth_bloc, it could become a mess injecting all the blocs as dependencies. What alternative could be a good solution?

  2. For the navigation I have a bloc for bottom navigation bar. But I want to all the screens on tabs can call the change_tab() function at bottom bar bloc. And again is the same problem, I only can add events at bottom bar bloc if the other blocs have a dependency on bottom bar bloc. I dont want to add the auth bloc and the bottom bar bloc as dependencies on all the blocs that manages all the screens.

I appreciate advices, alternatives or documentation where I can learn to build a good architecture for apps with bloc.

question

Most helpful comment

@sdstolworthy sure thing! I'll try to create a standalone example over the next few days 馃憤

All 10 comments

Hi @limonadev 馃憢
Thanks for opening an issue!

Regarding your questions:

  1. Your blocs should not care about tokens because that is a networking layer detail. Ideally, the bloc should invoke a method on the repository (getPosts). The repository would then invoke a get on the PostsApiClient which has a dependency on an HttpClient. You could then use http interceptors to handle automatically reading the auth token from keystore/keychain using flutter_secure_storage and appending it to the request header.

  2. Can you provide some more detail? If you provide a TabBarBloc then all children in the subtree can access the TabBarBloc via BuildContext like BlocProvider.of<TabBarBloc>(context). You can then add the event in response to onPress or other callbacks in the presentation layer. If you want to add an event in response to a state change in another bloc, I would recommend using BlocListener like:

BlocListener<OtherBloc, OtherBlocState>(
  listener: (context, state) {
    if (state is DesiredState) {
      BlocProvider.of<TabBarBloc>(context).add(ChangeTab(...));
    }
  },
  child: MyChild(),
)

I would also like to add that you might not even need a bloc for the bottom navigation bar because if there's no business logic involved you should simply do the navigation within the widget itself.

Hope that helps 馃憤

Thank you very much for your answer.

  1. I used the auth_bloc because if the token is expired, the app will try to login in the user automatically (I save the email and password). If that login attempt fails I need to return to the login page. With your method, I dont know how to notify to the app that needs rebuild to show the login page. My solution was to add the auth_bloc dependency on every bloc so when a repository (any of them) returns an error for token expired, I can add an event to auth_bloc from any of the blocs to rebuild the app (the auth_bloc is similar to the auth_bloc at this example, it controls if the app shows the login screen or the home screen). But my solution seemed like a bad design because I have to add the dependency to auth_bloc at every bloc on my app.
  2. If I want a lot of blocs that can call change_tab() I will need to add a listener for each one, right?

Again, thank you very much for your help.

No problem!

  1. You can just have a callback in your HttpClient called onTokenExpired and you can add a AuthenticationRevoked event to the AuthenticationBloc in that case.
final _httpClient = HttpClient(onTokenExpired: () => _authenticationBloc.add(AuthenticationRevoked()));
  1. You just need one listener and you can notify all blocs within the same listener. I'm not sure I fully understand your use case but you most likely should not need more than one BlocListener for a given bloc.

Closing for now but feel free to add additional comments/questions and I'm happy to continue the conversation 馃憤

@felangel Oh my goodness, I've been racking my head on how to do exactly what you're describing with the authentication error. Would you mind providing a more detailed example? I have not been able to figure out how to make this work in my own app. I hate how I'm handling authentication errors in my application right now.

@sdstolworthy sure thing! I'll try to create a standalone example over the next few days 馃憤

Thanks @felangel

@felangel I am sorry I need to reopen the question. In your answer

final _httpClient = HttpClient(onTokenExpired: () => _authenticationBloc.add(AuthenticationRevoked()));

the _httpClient has a dependency on _authenticationBloc. So, all the http clients the app create (for each data provider) should have that dependency (unless it becomes singleton). Unless I had misunderstood.
Also, suppose I have an AuthBloc with a dependency on a UserRepository (just like the example on Firebase Auth). If the UserRepository has a dependency on some ApiClient, the order of "creation" would be:

  1. Instance the ApiClient
  2. Instance the UserRepository and inject the ApiClient from 1.
  3. Instance the AuthBloc and inject the UserRepository from 2.

But if I want to have a callback onTokenExpired on the http client, I should pass it on the first step (in the ApiClient). But I can't have that callback because the AuthBloc is instanced on step 3. Is a cyclic dependency! (Again, sorry if I misunderstood something).

If somehow that is solved, I still need to communicate some Event from XBloc to the AuthBloc. For example, if I have 5 blocs (BlocA, BlocB, etc), and all of them had their own repos, I would need to communicate from any of them "Hey AuthBloc, the token has expired so I need to go to the login". That can be made injecting the AuthBloc to each one, but is that the intended way to work? I mean, each time I create a new Bloc I would have to pass the AuthBloc to it.

I tried some ideas, but even if they work, I think they are not very maintainable. For example, I saved an "status" on the UserRepository and I made it Singleton. I subscribed the AuthBloc to the status and when the status if "token expired" the AuthBloc react to it. And every query from other repositories (called from other Blocs) that use the token call the Singleton UserRepository to get the token. If the token has expired the status change and the AuthBloc rebuild the login, but the query feels "incomplete" because the Bloc that called it never got a response. I think mine it's a bad way to handle this, so if you can help me with some advices or a simple example about how is the correct way I will appreciate that a lot. Thanks.

@limonadev I'm working on a sample app for this since it's a highly requested item. I'll comment with a link once it's ready 馃憤

@felangel Thank you very much! :)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

MahdiPishguy picture MahdiPishguy  路  3Comments

Reidond picture Reidond  路  3Comments

shawnchan2014 picture shawnchan2014  路  3Comments

tigranhov picture tigranhov  路  3Comments

nhwilly picture nhwilly  路  3Comments