Skip to content

Latest commit

 

History

History
375 lines (192 loc) · 18.3 KB

flutterangulargithubsearch.md

File metadata and controls

375 lines (192 loc) · 18.3 KB

Flutter + AngularDart Github Search Tutorial

advanced

In the following tutorial, we're going to build a Github Search app in Flutter and AngularDart to demonstrate how we can share the data and business logic layers between the two projects.

demo

demo

Key Topics

  • BlocProvider, a Flutter widget which provides a bloc to its children.
  • BlocBuilder, a Flutter widget that handles building the widget in response to new states.
  • Using Bloc instead of Cubit. What's the difference?
  • Prevent unnecessary rebuilds with Equatable.
  • Use a custom EventTransformer with bloc_concurrency.
  • Making network requests using the http package.

Common Github Search Library

The Common Github Search library will contain models, the data provider, the repository, as well as the bloc that will be shared between AngularDart and Flutter.

Setup

We'll start off by creating a new directory for our application.

setup.sh

Next, we'll create the scaffold for the common_github_search library.

setup.sh

We need to create a pubspec.yaml with the required dependencies.

pubspec.yaml

Lastly, we need to install our dependencies.

pub_get.sh

That's it for the project setup! Now we can get to work on building out the common_github_search package.

Github Client

The GithubClient which will be providing raw data from the Github API.

?> Note: You can see a sample of what the data we get back will look like here.

Let's create github_client.dart.

github_client.dart

?> Note: Our GithubClient is simply making a network request to Github's Repository Search API and converting the result into either a SearchResult or SearchResultError as a Future.

?> Note: The GithubClient implementation depends on SearchResult.fromJson, which we have not yet implemented.

Next we need to define our SearchResult and SearchResultError models.

Search Result Model

Create search_result.dart, which represents a list of SearchResultItems based on the user's query:

search_result.dart

?> Note: The SearchResult implementation depends on SearchResultItem.fromJson, which we have not yet implemented.

?> Note: We aren't including properties that aren't going to be used in our model.

Search Result Item Model

Next, we'll create search_result_item.dart.

search_result_item.dart

?> Note: Again, the SearchResultItem implementation dependes on GithubUser.fromJson, which we have not yet implemented.

Github User Model

Next, we'll create github_user.dart.

github_user.dart

At this point, we have finished implementing SearchResult and its dependencies. Now we'll move onto SearchResultError.

Search Result Error Model

Create search_result_error.dart.

search_result_error.dart

Our GithubClient is finished so next we'll move onto the GithubCache, which will be responsible for memoizing as a performance optimization.

Github Cache

Our GithubCache will be responsible for remembering all past queries so that we can avoid making unnecessary network requests to the Github API. This will also help improve our application's performance.

Create github_cache.dart.

github_cache.dart

Now we're ready to create our GithubRepository!

Github Repository

The Github Repository is responsible for creating an abstraction between the data layer (GithubClient) and the Business Logic Layer (Bloc). This is also where we're going to put our GithubCache to use.

Create github_repository.dart.

github_repository.dart

?> Note: The GithubRepository has a dependency on the GithubCache and the GithubClient and abstracts the underlying implementation. Our application never has to know about how the data is being retrieved or where it's coming from since it shouldn't care. We can change how the repository works at any time and as long as we don't change the interface we shouldn't need to change any client code.

At this point, we've completed the data provider layer and the repository layer so we're ready to move on to the business logic layer.

Github Search Event

Our Bloc will be notified when a user has typed the name of a repository which we will represent as a TextChanged GithubSearchEvent.

Create github_search_event.dart.

github_search_event.dart

?> Note: We extend Equatable so that we can compare instances of GithubSearchEvent. By default, the equality operator returns true if and only if this and other are the same instance.

Github Search State

Our presentation layer will need to have several pieces of information in order to properly lay itself out:

  • SearchStateEmpty- will tell the presentation layer that no input has been given by the user.

  • SearchStateLoading- will tell the presentation layer it has to display some sort of loading indicator.

  • SearchStateSuccess- will tell the presentation layer that it has data to present.

    • items- will be the List<SearchResultItem> which will be displayed.
  • SearchStateError- will tell the presentation layer that an error has occurred while fetching repositories.

    • error- will be the exact error that occurred.

We can now create github_search_state.dart and implement it like so.

github_search_state.dart

?> Note: We extend Equatable so that we can compare instances of GithubSearchState. By default, the equality operator returns true if and only if this and other are the same instance.

Now that we have our Events and States implemented, we can create our GithubSearchBloc.

Github Search Bloc

Create github_search_bloc.dart:

github_search_bloc.dart

?> Note: Our GithubSearchBloc converts GithubSearchEvent to GithubSearchState and has a dependency on the GithubRepository.

?> Note: We create a custom EventTransformer to debounce the GithubSearchEvents. One of the reasons why we created a Bloc instead of a Cubit was to take advantage of stream transformers.

Awesome! We're all done with our common_github_search package. The finished product should look like this.

Next, we'll work on the Flutter implementation.

Flutter Github Search

Flutter Github Search will be a Flutter application which reuses the models, data providers, repositories, and blocs from common_github_search to implement Github Search.

Setup

We need to start by creating a new Flutter project in our github_search directory at the same level as common_github_search.

flutter_create.sh

Next, we need to update our pubspec.yaml to include all the necessary dependencies.

pubspec.yaml

?> Note: We are including our newly created common_github_search library as a dependency.

Now, we need to install the dependencies.

flutter_packages_get.sh

That's it for project setup. Since the common_github_search package contains our data layer as well as our business logic layer, all we need to build is the presentation layer.

Search Form

We're going to need to create a form with a _SearchBar and _SearchBody widget.

  • _SearchBar will be responsible for taking user input.
  • _SearchBody will be responsible for displaying search results, loading indicators, and errors.

Let's create search_form.dart.

Our SearchForm will be a StatelessWidget which renders the _SearchBar and _SearchBody widgets.

search_form.dart

Next, we'll implement _SearchBar.

Search Bar

_SearchBar is also going to be a StatefulWidget because it will need to maintain its own TextEditingController so that we can keep track of what a user has entered as input.

search_form.dart

?> Note: _SearchBar accesses GitHubSearchBloc via context.read<GithubSearchBloc>() and notifies the bloc of TextChanged events.

We're done with _SearchBar, now onto _SearchBody.

Search Body

_SearchBody is a StatelessWidget which will be responsible for displaying search results, errors, and loading indicators. It will be the consumer of the GithubSearchBloc.

search_form.dart

?> Note: _SearchBody uses BlocBuilder in order to rebuild in response to state changes. Since the bloc parameter of the BlocBuilder object was omitted, BlocBuilder will automatically perform a lookup using BlocProvider and the current BuildContext. Read more here.

If our state is SearchStateSuccess, we render _SearchResults which we will implement next.

Search Results

_SearchResults is a StatelessWidget which takes a List<SearchResultItem> and displays them as a list of _SearchResultItems.

search_form.dart

?> Note: We use ListView.builder in order to construct a scrollable list of _SearchResultItem.

It's time to implement _SearchResultItem.

Search Result Item

_SearchResultItem is a StatelessWidget and is responsible for rendering the information for a single search result. It is also responsible for handling user interaction and navigating to the repository url on a user tap.

search_form.dart

?> Note: We use the url_launcher package to open external urls.

Putting it all together

At this point our search_form.dart should look like

search_form.dart

Now all that's left to do is implement our main app in main.dart.

main.dart

?> Note: Our GithubRepository is created in main and injected into our App. Our SearchForm is wrapped in a BlocProvider which is responsible for initializing, closing, and making the instance of GithubSearchBloc available to the SearchForm widget and its children.

That’s all there is to it! We’ve now successfully implemented a GitHub search app in Flutter using the bloc and flutter_bloc packages and we’ve successfully separated our presentation layer from our business logic.

The full source can be found here.

Finally, we're going to build our AngularDart Github Search app.

AngularDart Github Search

AngularDart Github Search will be an AngularDart application which reuses the models, data providers, repositories, and blocs from common_github_search to implement Github Search.

Setup

We need to start by creating a new AngularDart project in our github_search directory at the same level as common_github_search.

stagehand.sh

!> Activate stagehand by running pub global activate stagehand.

We can then go ahead and replace the contents of pubspec.yaml with:

pubspec.yaml

Search Form

Just like in our Flutter app, we're going to need to create a SearchForm with a SearchBar and SearchBody component.

Our SearchForm component will implement OnInit and OnDestroy because it will need to create and close a GithubSearchBloc.

  • SearchBar will be responsible for taking user input.
  • SearchBody will be responsible for displaying search results, loading indicators, and errors.

Let's create search_form_component.dart.

search_form_component.dart

?> Note: The GithubRepository is injected into the SearchFormComponent.

?> Note: The GithubSearchBloc is created and closed by the SearchFormComponent.

Our template (search_form_component.html) will look like:

search_form_component.html

Next, we'll implement the SearchBar component.

Search Bar

SearchBar is a component which will be responsible for taking in user input and notifying the GithubSearchBloc of text changes.

Create search_bar_component.dart.

search_bar_component.dart

?> Note: SearchBarComponent has a dependency on GitHubSearchBloc because it is responsible for notifying the bloc of TextChanged events.

Next, we can create search_bar_component.html.

search_bar_component.html

We're done with SearchBar, now onto SearchBody.

Search Body

SearchBody is a component which will be responsible for displaying search results, errors, and loading indicators. It will be the consumer of the GithubSearchBloc.

Create search_body_component.dart.

search_body_component.dart

?> Note: SearchBodyComponent has a dependency on GithubSearchState which is provided by the GithubSearchBloc using the angular_bloc bloc pipe.

Create search_body_component.html.

search_body_component.html

If our state isSuccess, we render SearchResults. We will implement it next.

Search Results

SearchResults is a component which takes a List<SearchResultItem> and displays them as a list of SearchResultItems.

Create search_results_component.dart.

search_results_component.dart

Next up we'll create search_results_component.html.

search_results_component.html

?> Note: We use ngFor in order to construct a list of SearchResultItem components.

It's time to implement SearchResultItem.

Search Result Item

SearchResultItem is a component that is responsible for rendering the information for a single search result. It is also responsible for handling user interaction and navigating to the repository url on a user tap.

Create search_result_item_component.dart.

search_result_item_component.dart

and the corresponding template in search_result_item_component.html.

search_result_item_component.html

Putting it all together

We have all of our components and now it's time to put them all together in our app_component.dart.

app_component.dart

?> Note: We're creating the GithubRepository in the AppComponent and injecting it into the SearchForm component.

That’s all there is to it! We’ve now successfully implemented a github search app in AngularDart using the bloc and angular_bloc packages and we’ve successfully separated our presentation layer from our business logic.

The full source can be found here.

Summary

In this tutorial we created a Flutter and AngularDart app while sharing all of the models, data providers, and blocs between the two.

The only thing we actually had to write twice was the presentation layer (UI) which is awesome in terms of efficiency and development speed. In addition, it's fairly common for web apps and mobile apps to have different user experiences and styles and this approach really demonstrates how easy it is to build two apps that look totally different but share the same data and business logic layers.

The full source can be found here.