Introduction to flutter – bloc mode

Time:2020-11-24

The original address is here, written by Brian kayfitz.

Here’s a little bit about ide. Many people are mobile, so many people use Android studio. In fact, flutter can also be developed with vs code. I have used both of them. They have their own advantages. Android studio is convenient when there are many directories and files in the initial stage of the project. When refactoring, the modification of the file will be modified together with other file references, and there will be a prompt for deletion. In vs code, you have to manually change the file nameimportIt’s been changed together. However, vs code debugging is much more convenient. However, when debugging the real machine, you should remember firstSelect Device

text

Designing the architecture of an app is often controversial. Everyone has a cool architecture and a bunch of terms that they like.

Both IOS and Android developers are very familiar with MVC, and use this pattern as the default architecture during development. Model and view are separated, and controller is used as a bridge between them.

However, a set of responsive design brought by flutter is not well compatible with MVC. A new architecture based on this classic pattern appears in the flutter community–BLoC

Bloc isBusiness Logic CShort for components. The philosophy of bloc is that everything in an app should be considered an event flow: some components subscribe to events, and others respond to events. Bloc manages these sessions in the middle. Dart even built stream into the language itself.

The best thing about this model is that you don’t need to introduce any plug-ins or learn any other syntax. All the content you need is available from flutter.

In this article, we want to create a new app to find restaurants. API hasZomatoProvide. Finally, you will learn the following:

  • Wrapping API calls in bloc mode
  • Find and display results asynchronously
  • Maintain a list of favorite restaurants that can be accessed from multiple pages

start

Download the start project code here and open it with your favorite ide. Remember to run at the beginningflutter pub getIn the IDE or on the command line. After all the dependencies have been downloaded, you can start coding.

The basic model file and network request file are included in the initial project. It looks like this:

Introduction to flutter - bloc mode

Get the key of the API

Before we start to develop the application, we should first obtain a key of the API we want to use. At zomato’s developer site https://developers.zomato.com/api , register and generate a key.

stayDataLayerOpen the directoryzomato_client.dartDocuments. Modify this constant value:

class ZomatoClient {
  final _apiKey = "Your api key here";
}

In actual development, it’s not wise to put key into source code or mix it into version control tools。 It’s just for convenience, not for actual development.

Run it, you will see this effect:
Introduction to flutter - bloc mode

Black, now add code:

Let’s bake a multilayer cake

When writing an app, whether you’re using flutter or other frameworks, it’s critical to layer classes. It’s more like an informal convention, not necessarily in the code.

Each layer, or a set of classes, is responsible for an overall responsibility. There is a directory in the initial projectDataLayer。 This data layer is specially used for model of app and communication with background. It knows nothing about the UI.

Every app is different, but in general, you build something like this:

Introduction to flutter - bloc mode

This architecture convention is not too different from MVC. The UI / router layer can only communicate with the bloc layer, which processes logic and sends events to the data layer and UI. This structure can ensure the smooth expansion of the app when the scale becomes larger.

Go deep into bloc

Bloc is basically based on dart stream.

Introduction to flutter - bloc mode

Stream, like future, is also indart:asyncIn the bag. A stream is like a future. The difference is that a stream does not return a value asynchronously. A stream can return many values over time. If a future is ultimately a value, then a stream can return a series of values over time.

dart:asyncThe package provides aStreamControllerClass. The flow controller manages two object streams and sinks. A sink corresponds to a stream, which provides data and a sink accepts input values.

In summary, bloc is used to process logic, sink accepts input and stream output.

Positioning interface

Before looking for restaurants, you need to tell zomato where you want to eat. In this section, you’ll create a new simple interface with a search bar and a list to display the search results.

Don’t forget to open it before entering the codeDartFmt。 This is the best way to write a flutter app.

staylib/UITable of contents, one in betweenlocation_screen.dartDocuments. Add oneStatelessWidget, and namedLocationScreen

import 'package:flutter/material.dart';
class LocationScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Where do you want to eat?')),
      body: Column(
        children: <Widget>[
          Padding(
            padding: const EdgeInsets.all(10.0),
            child: TextField(
              decoration: InputDecoration(
                  border: OutlineInputBorder(), hintText: 'Enter a location'),
              onChanged: (query) { },
            ),
          ),
          Expanded(
            child: _buildResults(),
          )
        ],
      ),
    );
  }


  Widget _buildResults() {
    return Center(child: Text('Enter a location'));
  }
 }

The positioning interface contains aTextFieldThe user can enter the location here.

Your ide will report an error if the class you input is not imported. To correct this error, just move the cursor over the identifier and press appleoption+enter(ALT + enter under Windows) or click the red light bulb on the edge. After you click, a menu will appear. Select import and it will be OK.

Add another filemain_screen.dartFile, which will be used to manage the navigation of the interface. Add the following code:

class MainScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return LocationScreen();
  }
}

to updatemain.dartDocument:

MaterialApp(
  title: 'Restaurant Finder',
  theme: ThemeData(
    primarySwatch: Colors.red,
  ),
  home: MainScreen(),
),

Now run the code like this:
Introduction to flutter - bloc mode

Now it’s time for bloc.

First bloc

staylibCreate aBLoCcatalog. This is used to store all the bloc classes.

Create a new onebloc.dartFile, add the following code:

abstract class Bloc {
  void dispose();
}

All bloc classes follow this interface. This interface does nothing but force your code to include adispoosemethod. It is very important to turn off the stream when it is not in use, otherwise it will cause memory leakage. YesdisposeMethod, which is called directly by app.

The first bloc will handle the location selected by the app.

stayBLoCDirectory, create a new filelocation_bloc.dart。 Add the following code:

class LocationBloc implements Bloc {
  Location _location;
  Location get selectedLocation => _location;

  // 1
  final _locationController = StreamController<Location>();

  // 2
  Stream<Location> get locationStream => _locationController.stream;

  // 3
  void selectLocation(Location location) {
    _location = location;
    _locationController.sink.add(location);
  }

  // 4
  @override
  void dispose() {
    _locationController.close();
  }
}

useoption+enterImport bloc class.
Introduction to flutter - bloc mode

LocationBlocIt mainly deals with the following matters:

  1. There’s a private oneStreamControllerTo manage flows and sinks.StreamControllerUse generics to tell the calling code what type of data is returned.
  2. This line uses getters to expose the stream
  3. This method is used to enter a value for bloc. And the location data is also cached in the_locationAttribute.
  4. Finally, in the cleaning methodStreamControllerThe object is closed before it is recycled. If you don’t, your ide will also display errors.

Now that your first bloc is complete, the next step is to find a location.

The second bloc

stayBLoCCreate a new one in the directorylocation_query_bloc.dartFile and add the following code:

class LocationQueryBloc implements Bloc {
  final _controller = StreamController<List<Location>>();
  final _client = ZomatoClient();
  Stream<List<Location>> get locationStream => _controller.stream;

  void submitQuery(String query) async {
    // 1
    final results = await _client.fetchLocations(query);
    _controller.sink.add(results);
  }

  @override
  void dispose() {
    _controller.close();
  }
}

stay//1This method takes a string parameter and uses theZomatoClientClass to get location data. It’s used hereasync/awaitTo make the code look clearer. The result is then pushed into the stream.

This bloc is basically similar to the previous one, except that it also contains an API request.

Combining bloc with component tree

Now that you have two blocs, you need to combine them with the components. This method is basically called flutterprovider。 A provider provides data for this component and its subcomponents.

Generally speaking, this isInheritedWidgetBut because bloc needs to be released,StatefulWidgetThe same service will be provided. So the syntax is a little more complicated, but the result is the same.

stayBLoCCreate a new onebloc_provider.dartFile and add the following code:

// 1
class BlocProvider<T extends Bloc> extends StatefulWidget {
  final Widget child;
  final T bloc;

  const BlocProvider({Key key, @required this.bloc, @required this.child})
      : super(key: key);

  // 2
  static T of<T extends Bloc>(BuildContext context) {
    final type = _providerType<BlocProvider<T>>();
    final BlocProvider<T> provider = context.ancestorWidgetOfExactType(type);
    return provider.bloc;
  }

  // 3
  static Type _providerType<T>() => T;

  @override
  State createState() => _BlocProviderState();
}

class _BlocProviderState extends State<BlocProvider> {
  // 4
  @override
  Widget build(BuildContext context) => widget.child;

  // 5
  @override
  void dispose() {
    widget.bloc.dispose();
    super.dispose();
  }
}

The above code is parsed as follows:

  1. BlocProviderIs a generic class. typeTThe requirements must be fulfilledBlocInterface. This means that the provider can only store objects of type bloc.
  2. ofMethod allows the component to get theBlocProvider。 This is the normal operation of the flutter.
  3. Here is the object to get a generic type
  4. thisbuildMethods don’t build anything
  5. Finally, why does this provider inheritStatefulWidgetIt’s mainly for the sake ofdisposemethod. When a component is removed from the tree, flutter callsdisposeMethod to close the flow

Combined positioning interface

Now that you have the complete bloc layer code to find the location, it’s time to use it.

First of all, in themain.dartA bloc is used to wrap the material app. The easiest thing is to move the cursor toMaterialAppUp, downoption+enter(Windows uses Alt + Enter), a menu will pop up, selectWrap with a new widget

Note: this code was received from Didier boelens https://www.didierboelens.com… —streams—bloc/。 The inspiration. This component has not been optimized, but it can be optimized in theory. This article will continue to use a more initial approach, as this will satisfy most scenarios. If you find performance problems later, you can find improvements in the flutter bloc package.

Then the code goes like this:

return BlocProvider<LocationBloc>(
  bloc: LocationBloc(),
  child: MaterialApp(
    title: 'Restaurant Finder',
    theme: ThemeData(
      primarySwatch: Colors.red,
    ),
    home: MainScreen(),
  ),
);

The simplest way to pass data to the provider is to pass the data to the provider.

staymain_screen.dartDocuments do the same thing. stayLocationScreen.dartUp and downoption + enter, select * * wrap with streambuilder ‘. The updated code looks like this:

return StreamBuilder<Location>(
  // 1
  stream: BlocProvider.of<LocationBloc>(context).locationStream,
  builder: (context, snapshot) {
    final location = snapshot.data;

    // 2
    if (location == null) {
      return LocationScreen();
    }
    
    // This will be changed this later
    return Container();
  },
);

StreamBuilderIt is the catalyst of bloc mode. These components automatically listen for events in the stream. When a new event is received,builderMethod is executed to update the component tree. useStreamBuilderAnd bloc mode is completely unnecessarysetStateThe way.

Code analysis:

  1. streamProperties, usingofMethodLocationBlocAnd give the flow toStreamBuilder
  2. At first, there is no data in the stream, which is normal. If there is no data, the app returnsLocationScreen。 Otherwise, a blank interface will be returned temporarily.

Next, in thelocation_screen.dartIt’s used insideLocationQueryBlocUpdate the positioning interface. Don’t forget to use the shortcut keys provided by the IDE to update the code:

@override
Widget build(BuildContext context) {
  // 1
  final bloc = LocationQueryBloc();

  // 2
  return BlocProvider<LocationQueryBloc>(
    bloc: bloc,
    child: Scaffold(
      appBar: AppBar(title: Text('Where do you want to eat?')),
      body: Column(
        children: <Widget>[
          Padding(
            padding: const EdgeInsets.all(10.0),
            child: TextField(
              decoration: InputDecoration(
                  border: OutlineInputBorder(), hintText: 'Enter a location'),
              
              // 3
              onChanged: (query) => bloc.submitQuery(query),
            ),
          ),
          // 4
          Expanded(
            child: _buildResults(bloc),
          )
        ],
      ),
    ),
  );
}

The analysis is as follows:

  1. First of all, in thebuildMethod initializes aLocationQueryBlocClass.
  2. The bloc is then stored in theBlocProviderinside
  3. to updateTextFieldOfonChangeMethod, the modified text is submitted to theLocationQueryBlocObject. This initiates the request API and returns the chain reaction of the data.
  4. Passing the bloc object to_buildResultmethod.

toLocationScreenAdd a bool member at a time to mark whether it is a full screen dialog.

class LocationScreen extends StatelessWidget {
  final bool isFullScreenDialog;
  const LocationScreen({Key key, this.isFullScreenDialog = false})
      : super(key: key);
  ...  

This bool is just a simple tag. It will be used when selecting a location in the future.

Update now_buildResultsmethod. Add a stream builder to display the results in a list. You can use itWrap with StreamBuilderTo quickly update the code:

Widget _buildResults(LocationQueryBloc bloc) {
  return StreamBuilder<List<Location>>(
    stream: bloc.locationStream,
    builder: (context, snapshot) {

      // 1
      final results = snapshot.data;
    
      if (results == null) {
        return Center(child: Text('Enter a location'));
      }
    
      if (results.isEmpty) {
        return Center(child: Text('No Results'));
      }
    
      return _buildSearchResults(results);
    },
  );
}

Widget _buildSearchResults(List<Location> results) {
  // 2
  return ListView.separated(
    itemCount: results.length,
    separatorBuilder: (BuildContext context, int index) => Divider(),
    itemBuilder: (context, index) {
      final location = results[index];
      return ListTile(
        title: Text(location.title),
        onTap: () {
          // 3
          final locationBloc = BlocProvider.of<LocationBloc>(context);
          locationBloc.selectLocation(location);

          if (isFullScreenDialog) {
            Navigator.of(context).pop();
          }
        },
      );
    },
  );
}

The code is parsed as follows:

  1. Stream can return three kinds of results: no data (the user did nothing), and an empty array, that is, zomato did not find a qualified result. Finally, a list of restaurants.
  2. Show a set of data returned. This is also the normal operation of the router
  3. onTapMethod, users click on a restaurant to obtainLocationBlocAnd jump back to the previous page

Run the code again. You can see this effect:

Introduction to flutter - bloc mode

There’s a little bit of progress.

Restaurant page

The second page of the app displays a group of restaurants based on the search results. It also has a corresponding bloc object to manage state.

stayBLoCDirectory create a new filerestaurant_bloc.dart。 And add the following code:

class RestaurantBloc implements Bloc {
  final Location location;
  final _client = ZomatoClient();
  final _controller = StreamController<List<Restaurant>>();

  Stream<List<Restaurant>> get stream => _controller.stream;
  RestaurantBloc(this.location);

  void submitQuery(String query) async {
    final results = await _client.fetchRestaurants(location, query);
    _controller.sink.add(results);
  }

  @override
  void dispose() {
    _controller.close();
  }
}

andLocationQueryBlocThe base class is similar. The only difference is the type of data returned.

Now inUICreate a new one in the directoryrestaurant_screen.dartFile. The new bloc is put into use

class RestaurantScreen extends StatelessWidget {
  final Location location;

  const RestaurantScreen({Key key, @required this.location}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(location.title),
      ),
      body: _buildSearch(context),
    );
  }

  Widget _buildSearch(BuildContext context) {
    final bloc = RestaurantBloc(location);

    return BlocProvider<RestaurantBloc>(
      bloc: bloc,
      child: Column(
        children: <Widget>[
          Padding(
            padding: const EdgeInsets.all(10.0),
            child: TextField(
              decoration: InputDecoration(
                  border: OutlineInputBorder(),
                  hintText: 'What do you want to eat?'),
              onChanged: (query) => bloc.submitQuery(query),
            ),
          ),
          Expanded(
            child: _buildStreamBuilder(bloc),
          )
        ],
      ),
    );
  }

  Widget _buildStreamBuilder(RestaurantBloc bloc) {
    return StreamBuilder(
      stream: bloc.stream,
      builder: (context, snapshot) {
        final results = snapshot.data;

        if (results == null) {
          return Center(child: Text('Enter a restaurant name or cuisine type'));
        }
    
        if (results.isEmpty) {
          return Center(child: Text('No Results'));
        }
    
        return _buildSearchResults(results);
      },
    );
  }

  Widget _buildSearchResults(List<Restaurant> results) {
    return ListView.separated(
      itemCount: results.length,
      separatorBuilder: (context, index) => Divider(),
      itemBuilder: (context, index) {
        final restaurant = results[index];
        return RestaurantTile(restaurant: restaurant);
      },
    );
  }
}

Create another onerestaurant_tile.dartTo show the details of the restaurant:

class RestaurantTile extends StatelessWidget {
  const RestaurantTile({
    Key key,
    @required this.restaurant,
  }) : super(key: key);

  final Restaurant restaurant;

  @override
  Widget build(BuildContext context) {
    return ListTile(
      leading: ImageContainer(width: 50, height: 50, url: restaurant.thumbUrl),
      title: Text(restaurant.name),
      trailing: Icon(Icons.keyboard_arrow_right),
    );
  }
}

This code looks very similar to the location interface code. The only difference is that it shows the restaurant, not the location.

modifymain_screen.dartinMainScreenCode for:

builder: (context, snapshot) {
  final location = snapshot.data;

  if (location == null) {
    return LocationScreen();
  }

  return RestaurantScreen(location: location);
},

After you select a location, a list of restaurants will be displayed.
Introduction to flutter - bloc mode

Favorite restaurant

So far, bloc has only been used to process user input. It can do more than that. Suppose users want to record their favorite restaurants and display them on a separate list page. This can also be solved by using the bloc mode.

stayBLoCDirectory, create a new onefavorite_bloc.dartFile to store this list:

class FavoriteBloc implements Bloc {
  var _restaurants = <Restaurant>[];
  List<Restaurant> get favorites => _restaurants;
  // 1
  final _controller = StreamController<List<Restaurant>>.broadcast();
  Stream<List<Restaurant>> get favoritesStream => _controller.stream;

  void toggleRestaurant(Restaurant restaurant) {
    if (_restaurants.contains(restaurant)) {
      _restaurants.remove(restaurant);
    } else {
      _restaurants.add(restaurant);
    }

    _controller.sink.add(_restaurants);
  }

  @override
  void dispose() {
    _controller.close();
  }
}

Code parsing: in// 1We use aBroadcastOfStreamController, rather than a regular oneStreamControllerBroadcastA stream of type can have multiple listeners, while a regular stream only allows one. In the first two blocs, there is only a one-to-one relationship, so there is no need for multiple listeners. For the favorite function, two places are needed to monitor, so broadcasting is a must.

Note: the general rule of using bloc is to use the regular stream first, and then refactor the code when broadcasting is needed. If more than one object is listening to the same regular stream, then flutter throws an exception. Use this as a sign that code needs to be refactored.

This bloc needs to be accessible to multiple pages, which means it should be placed outside the navigator. to updatemain.dartAdd the following components:

return BlocProvider<LocationBloc>(
  bloc: LocationBloc(),
  child: BlocProvider<FavoriteBloc>(
    bloc: FavoriteBloc(),
    child: MaterialApp(
      title: 'Restaurant Finder',
      theme: ThemeData(
        primarySwatch: Colors.red,
      ),
      home: MainScreen(),
    ),
  ),
);

Next, in theUIAdd afavorite_screen.dartDocuments. This component displays the user’s favorite restaurant:

class FavoriteScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final bloc = BlocProvider.of<FavoriteBloc>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text('Favorites'),
      ),
      body: StreamBuilder<List<Restaurant>>(
        stream: bloc.favoritesStream,
        // 1
        initialData: bloc.favorites,
        builder: (context, snapshot) {
          // 2
          List<Restaurant> favorites =
              (snapshot.connectionState == ConnectionState.waiting)
                  ? bloc.favorites
                  : snapshot.data;
    
          if (favorites == null || favorites.isEmpty) {
            return Center(child: Text('No Favorites'));
          }
    
          return ListView.separated(
            itemCount: favorites.length,
            separatorBuilder: (context, index) => Divider(),
            itemBuilder: (context, index) {
              final restaurant = favorites[index];
              return RestaurantTile(restaurant: restaurant);
            },
          );
        },
      ),
    );
  }
}

In this component:

  1. stayStreamBuilderAdd the initial data.StreamBuilderThe builder method is called immediately, even if there is no data.
  2. Check the connection status of the app.

Next, update thebuildMethod: add your favorite restaurant to the navigation

@override
Widget build(BuildContext context) {
  return Scaffold(
      appBar: AppBar(
        title: Text(location.title),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.favorite_border),
            onPressed: () => Navigator.of(context)
                .push(MaterialPageRoute(builder: (_) => FavoriteScreen())),
          )
        ],
      ),
      body: _buildSearch(context),
  );
}

You need another interface where users can set the restaurant as their favorite.

stayUINew under directoryrestaurant_details_screen.dartDocuments. The main codes are as follows:

class RestaurantDetailsScreen extends StatelessWidget {
  final Restaurant restaurant;

  const RestaurantDetailsScreen({Key key, this.restaurant}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final textTheme = Theme.of(context).textTheme;

    return Scaffold(
      appBar: AppBar(title: Text(restaurant.name)),
      body: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          _buildBanner(),
          Padding(
            padding: const EdgeInsets.all(8.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Text(
                  restaurant.cuisines,
                  style: textTheme.subtitle.copyWith(fontSize: 18),
                ),
                Text(
                  restaurant.address,
                  style: TextStyle(fontSize: 18, fontWeight: FontWeight.w100),
                ),
              ],
            ),
          ),
          _buildDetails(context),
          _buildFavoriteButton(context)
        ],
      ),
    );
  }

  Widget _buildBanner() {
    return ImageContainer(
      height: 200,
      url: restaurant.imageUrl,
    );
  }

  Widget _buildDetails(BuildContext context) {
    final style = TextStyle(fontSize: 16);

    return Padding(
      padding: EdgeInsets.only(left: 10),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.start,
        children: <Widget>[
          Text(
            'Price: ${restaurant.priceDisplay}',
            style: style,
          ),
          SizedBox(width: 40),
          Text(
            'Rating: ${restaurant.rating.average}',
            style: style,
          ),
        ],
      ),
    );
  }

  // 1
  Widget _buildFavoriteButton(BuildContext context) {
    final bloc = BlocProvider.of<FavoriteBloc>(context);
    return StreamBuilder<List<Restaurant>>(
      stream: bloc.favoritesStream,
      initialData: bloc.favorites,
      builder: (context, snapshot) {
        List<Restaurant> favorites =
            (snapshot.connectionState == ConnectionState.waiting)
                ? bloc.favorites
                : snapshot.data;
        bool isFavorite = favorites.contains(restaurant);

        return FlatButton.icon(
          // 2
          onPressed: () => bloc.toggleRestaurant(restaurant),
          textColor: isFavorite ? Theme.of(context).accentColor : null,
          icon: Icon(isFavorite ? Icons.favorite : Icons.favorite_border),
          label: Text('Favorite'),
        );
      },
    );
  }
}

Code analysis:

  1. This component uses theFavoriteBlocTo determine whether a restaurant is a favorite restaurant, and the corresponding update interface
  2. FavoriteBloc#toggleRestaurantMethod allows the component not to care about whether a restaurant is a favorite.

stayrestaurant_tile.dartDocument’sonTapMethod to add the following code:

onTap: () {
  Navigator.of(context).push(
    MaterialPageRoute(
      builder: (context) =>
          RestaurantDetailsScreen(restaurant: restaurant),
    ),
  );
},

Run code:

Introduction to flutter - bloc mode

Update positioning

What if users want to update the location they’re looking for? Now if you change the location, the app will have to restart.

Because you’ve made the code work on a set of data that is passed by the stream, adding a function becomes very simple, just like putting a cherry on a cake.

On the restaurant page, add a floating button. Click this button and the location page will pop up.

 ...
    body: _buildSearch(context),
    floatingActionButton: FloatingActionButton(
      child: Icon(Icons.edit_location),
      onPressed: () => Navigator.of(context).push(MaterialPageRoute(
          builder: (context) => LocationScreen(
                // 1
                isFullScreenDialog: true,
              ),
          fullscreenDialog: true)),
    ),
  );
}

stay// 1isFullScreenDialogSet to true so that the navigation page will be displayed in full screen after it pops up.

stayLocationScreenOfLisTile#onTapThis is how the method is usedisFullScreenDialogOf:

onTap: () {
  final locationBloc = BlocProvider.of<LocationBloc>(context);
  locationBloc.selectLocation(location);
  if (isFullScreenDialog) {
    Navigator.of(context).pop();
  }
},

This is done in order to remove the location when it is also displayed as a dialog box.

Run the code again, you will see a floating button, click it will pop up the location page.

Introduction to flutter - bloc mode

last

Congratulations on having learned the bloc mode. Bloc is a simple and powerful app state management mode.

You can download the final project code in this case. If you want to run it, remember to get an app key from zomato and update itzomato_client.dartCode (don’t put it in code version control, such as GitHub, etc.). Other modes that can be seen:

  • Provider:https://pub.dev/packages/prov…
  • Scoped Model:https://pub.dev/packages/prov…
  • RxDart:https://pub.dev/packages/prov…
  • Redux:https://pub.dev/packages/prov…

You can also view official documents, or Google IO videos.

I hope you like this blog tutorial and leave any questions in the comments section.