felangel / bloc

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

[Example request] api example with request queueing #336

Closed Tregan closed 5 years ago

Tregan commented 5 years ago

Heyo!

So, we've decided to start rewriting our app using Flutter, using Bloc for state management. We're kind of stuck with the api part. I've checked out the weather example, but what could we do to implement a queue of requests? It seems that the bloc would get really big if we have events and states for every request (to give an idea: we have about 50 api routes)

Thanks in advance!

Yours, Bas

felangel commented 5 years ago

Hi @Tregan πŸ‘‹ Thanks for opening an issue and for giving the bloc library a try!

Can you please provide a bit more information about what your concerns are? I’m not sure I understand what you mean by a queue of requests. If you can provide a code snippet of how you’re handling the issue without bloc it would be super helpful! Thanks πŸ‘

Tregan commented 5 years ago

Our app is currently made in Unity. Unity is way overkill for what we need, but at the time it was the only proper cross-platform solution around. The app supports offline-mode, so that means we have to fetch a lot of content once the user logs in, for example topics, questions, statistics, images, opponents. We need those requests to finish in a specific order (most important to least important), so we add everything to a queue. Once a request is finished, we fire an event so the listeners know that they have to refresh the views, and then next queued request is started.

What we're struggling with is how we'd tackle something like this in Flutter. Would we need a separate bloc/event/state class per request? What class would be keeping track of the requests? The repository, or the bloc, or something else? If we use 1 bloc for all requests, how would we handle events and states? What if some request is already running and we want to queue/start another one?

Looking at the weather example, the repository has 1 function that uses 2 api calls. Our repository would have a lot of functions with most of them using 1 api call, which could all be called in succession (queue) or while another request is already running.

An example of what we want: User enters credentials -> fetch session id -> fetch user data -> fetch organization -> fetch additional user data from 5 api routes -> go to home page -> fetch content (queue [all of these are paginated requests]: topics, certificates of those topics, questions of those topics, statistics of those questions, images of those questions, opponents of those topics)

Sorry if this only confuses you more, haha. We're probably still stuck in C# πŸ˜‚ Really appreciate the help though!

Yours, Bas

-Edit- Added example flow

craiglabenz commented 5 years ago

I'm not @felangel, but I can offer a little insight from my time using his library.

What class would be keeping track of the requests? The repository, or the bloc, or something else?

The bloc should call functions in the repository that, in turn, probably look through internal sources classes which check local memory, maybe a local file store cache (all this stuff is optional), and finally, at the bottom of the stack of sources, is an API source that makes network requests. This code is pulled straight from a side project of my own and lives in my BaseRepository class, and loops over its known sources to find data:

abstract class BaseRepository<T> {
  List<Source> sources;

  Future<List<T>> getItems(Map<String, dynamic> params) async {
    List<T> items = [];
    List<Source> emptySources = [];

    // First check local memory, then fall back to a network request to the API
    for (var source in sources) {
      List<T> _items = await source.getItems(params);
      if (_items != null) {
        items = _items;
        break;
      } else {

        // Note which sources came up empty. Only local storage sources actually matter
        // in this list
        emptySources.add(source);
      }
    }

    // Classic caching of data locally to prevent network calls next time
    for (final Source source in emptySources) {
      source.setItems(items);
    }
    return items;
  }
}

With the above example (if it makes any sense), hopefully you can see that you would not want a separate bloc and repository for request. But you probably do want a separate repository per model type (use generics to write it once!), and then a unique bloc for each logical section of your app. In my experience so far, I just have to play with that to figure out where to draw the lines between blocs. Maybe @felangel has more concrete rules of thumb.

Hope this clarifies some of it!

Tregan commented 5 years ago

@craiglabenz That already made things so much more clear, thanks! Let's see if I indeed understand it: Say we have 3 models user, organization, topic, question.

Then all these blocs are made available by the root MaterialApp using BlocProviderTree, so pages that need them can access them whenever. Whenever a user logged in and we have their id, we can dispatch FetchUser(userId) -> UserLoaded state is fired -> dispatch FetchOrganization(userId) -> OrganizationLoaded is fired. Same for the topics and questions. Does that sound about right? πŸ˜„ Or would it be better to, for example, have questions in their own repository that the topic knows about?

craiglabenz commented 5 years ago

At a glance, that all sounds exactly right, @Tregan. I also found that keeping all of my blocs at the top level and making them available anywhere via the BlocProvider widget was the best situation for my app.

felangel commented 5 years ago

Thanks @craiglabenz for your detailed answer! I really appreciate the help πŸ‘

I would just like to add that you should consider scoping the blocs only to the part of the widget tree that needs them instead of declaring them all at the root level.

In addition, your repositories should represent the different domains of your application whereas the blocs should be more feature-driven (as @craiglabenz mentioned). You might have a UserRepository and a OrganizationRepository with many blocs for each of your features that have a dependency on one or more repository.

If you haven't already, I highly recommend checking out the Bloc Architecture Docs.

Hope that helps! Closing this for now but feel free to comment with additional questions and I'm happy to continue the conversation πŸ˜„

Tregan commented 5 years ago

First of all: thanks for the replies guys, this is really helping a lot πŸ˜„

I would just like to add that you should consider scoping the blocs only to the part of the widget tree that needs them instead of declaring them all at the root level.

Makes sense, I don't really want the full application to have access to everything, but a lot of pages will need access to a lot of different blocs. Would it be a good idea to add the blocs to required parameters of the pages, so that they are passed to the pages by app in MaterialApp onGenerateRoute? Then the page state could ofcourse access the bloc using widget.blocVariable