Github Search and BLoC
Tags: computerscience, productivity, programming, flutter
Published: 2021-02-13 04:36:35

*In case it helped :)*
<a href="https://www.buymeacoffee.com/aseemwangoo" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Pass Me A Coffee!!" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>

<!-- wp:paragraph -->
<p><strong><em>Website: </em></strong><a href="https://web.flatteredwithflutter.com/#/" rel="noreferrer noopener" target="_blank"><strong><em>https://web.flatteredwithflutter.com/#/</em></strong></a></p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>We will cover briefly about</p>
<!-- /wp:paragraph -->

<!-- wp:list {"ordered":true} -->
<ol><li>Integrate Github API</li><li>Define UI states&nbsp;</li><li>Create Search BLoC</li><li>Update UI as per states</li></ol>
<!-- /wp:list -->

{% youtube 7eQgZ6QQwxs %}

<!-- wp:heading {"level":3} -->
<h3>Integrate Github&nbsp;API</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>We define an abstract class (aka <strong>contract</strong>), which includes one method.</p>
<!-- /wp:paragraph -->

<!-- wp:image -->
<figure class="wp-block-image"><img src="https://cdn-images-1.medium.com/max/1600/1*G4KaI2oqwmMPQ49ZLmwFVQ.png" alt="Github Search and BLoC"/><figcaption>Github Search and&nbsp;BLoC</figcaption></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>Github has a <a href="https://stackoverflow.com/questions/53747159/whats-the-correct-endpoint-to-access-github-repositories-on-github-api" rel="noreferrer noopener" target="_blank">public endpoint</a> exposed for searching the repositories, and we append the user-defined search term to it.</p>
<!-- /wp:paragraph -->

<!-- wp:preformatted -->
<pre class="wp-block-preformatted">https://api.github.com/search/repositories?q='YOUR SEARCH TERM'</pre>
<!-- /wp:preformatted -->

<!-- wp:paragraph -->
<p>So now, we implement the abstract class in our <strong>GithubApi</strong> (our implementation class name).</p>
<!-- /wp:paragraph -->

<!-- wp:preformatted -->
<pre class="wp-block-preformatted">class GithubApi implements GithubSearchContract</pre>
<!-- /wp:preformatted -->

<!-- wp:paragraph -->
<p>and our search function looks like this</p>
<!-- /wp:paragraph -->

<!-- wp:image -->
<figure class="wp-block-image"><img src="https://cdn-images-1.medium.com/max/1600/1*5I2qKaWZ8F7We_yv3h6m0w.png" alt="Github Search and BLoC"/></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>where we call the API, fetch the results, and convert them into the <strong>SearchResult</strong> model.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3>Define UI&nbsp;states</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>We formulate all the possible states our UI can have and then define them.</p>
<!-- /wp:paragraph -->

<!-- wp:preformatted -->
<pre class="wp-block-preformatted">enum States {<br>  noTerm,<br>  error,<br>  loading,<br>  populated,<br>  empty,<br>}</pre>
<!-- /wp:preformatted -->

<!-- wp:paragraph -->
<p>We create a base class (<strong>SearchState</strong>), and each state (defined above) will implement this base class.</p>
<!-- /wp:paragraph -->

<!-- wp:preformatted -->
<pre class="wp-block-preformatted">@immutable
class SearchState extends BlocState {
  SearchState({this.state});
  final States state;
}

abstract class BlocState extends Equatable {
  @override
  List&lt;Object> get props => [];
}</pre>
<!-- /wp:preformatted -->

<!-- wp:paragraph -->
<p>Our <strong>SearchState </strong>class is internally extending <a rel="noreferrer noopener" href="https://pub.dev/packages/equatable" target="_blank">equatable</a>. Equatable does the heavy lifting for equality comparisons between two objects.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":4} -->
<h4>Implement UI&nbsp;states</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>All the values inside the enum correspond to a UI state, currently, we have <strong>5 values inside our enum</strong>, hence we will <strong>create 5 states</strong>.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>class SearchNoTerm extends SearchState {
  SearchNoTerm() : super(state: States.noTerm);
}
class SearchError extends SearchState {
  SearchError() : super(state: States.error);
}
class SearchLoading extends SearchState {
  SearchLoading() : super(state: States.loading);
}
class SearchPopulated extends SearchState {
  final SearchResult result;
  SearchPopulated(this.result) : super(state: States.populated);
}
class SearchEmpty extends SearchState {
  SearchEmpty() : super(state: States.empty);
}</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>As we see here, each of the UI states also includes the respective value from the enum. For instance,</p>
<!-- /wp:paragraph -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p><strong>SearchNoTerm state</strong> has the value of <strong>States.noTerm</strong>&nbsp;, and so on</p></blockquote>
<!-- /wp:quote -->

<!-- wp:paragraph -->
<p>The results are only included in the <strong>SearchPopulated</strong> <strong>state</strong>, which has a <strong>SearchResult(our model class)</strong> parameter.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3>Create Search&nbsp;BLoC</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The time has come, to create our much-anticipated BLoC.</p>
<!-- /wp:paragraph -->

<!-- wp:image -->
<figure class="wp-block-image"><img src="https://cdn-images-1.medium.com/max/1600/1*T9wvQB0FjG481mqykxr1sQ.png" alt="Github Search and BLoC"/><figcaption>Github Search and&nbsp;BLoC</figcaption></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>The idea behind bloc is to <strong>expose</strong> <strong>sinks</strong> (for user-defined events) and react as per those events by <strong>emitting the respective states</strong>.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>We define our search bloc which takes in the <strong>implementation of Github API as a parameter</strong>.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>class SearchBloc {
  factory SearchBloc(GithubSearchContract api) {
   //.....
  }
  
  // Sink exposed to UI
  final Sink&lt;String> onTextChanged;
  // State exposed to UI
  final Stream&lt;SearchState> state;
}</code></pre>
<!-- /wp:code -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p>We expose the <strong>onTextChanged sink</strong> and emit the <strong>stream of searchstate</strong>.</p></blockquote>
<!-- /wp:quote -->

<!-- wp:heading {"level":4} -->
<h4>1. onTextChanged Sink</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>We use <a href="https://pub.dev/packages/rxdart" rel="noreferrer noopener" target="_blank">RxDart</a> for defining what goes inside our sink.</p>
<!-- /wp:paragraph -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p>RxDart adds additional capabilities to Dart <a href="https://api.dart.dev/stable/dart-async/Stream-class.html" rel="noreferrer noopener" target="_blank">Streams</a> and <a href="https://api.dart.dev/stable/dart-async/StreamController-class.html" rel="noreferrer noopener" target="_blank">StreamControllers</a>.</p></blockquote>
<!-- /wp:quote -->

<!-- wp:code -->
<pre class="wp-block-code"><code>factory SearchBloc(GithubSearchContract api) {
  final onTextChanged = PublishSubject&lt;String>();
  final state = onTextChanged
        .distinct()
        .debounceTime(const Duration(milliseconds: 500))
        .switchMap&lt;SearchState>((String term) =>                                                _helpers.eventTyping(term))
        .startWith(SearchNoTerm());
  return SearchBloc._(api, onTextChanged, state);
}</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>We create a <a href="https://pub.dev/documentation/rxdart/latest/rx/PublishSubject-class.html" rel="noreferrer noopener" target="_blank"><strong>PublishSubject</strong></a> of type string as we would be searching a string term.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><strong>PublishSubject</strong>: It emits all the subsequent items of the source Observable at the time of subscription.</p>
<!-- /wp:paragraph -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p>Unlike a <code>BehaviorSubject</code>, a <code>PublishSubject</code> doesn't retain/cache items, therefore, a new <code>Observer</code> won't receive any past items</p></blockquote>
<!-- /wp:quote -->

<!-- wp:code -->
<pre class="wp-block-code"><code>final subject = PublishSubject&lt;int>();

// observer1 will receive all data and done events
subject.stream.listen(observer1);
subject.add(1);
subject.add(2);

// observer2 will only receive 3 and done event
subject.stream.listen(observe2);
subject.add(3);
subject.close();</code></pre>
<!-- /wp:code -->

<!-- wp:image -->
<figure class="wp-block-image"><img src="https://cdn-images-1.medium.com/max/1600/0*FL3hhOXm-oNiJSU2.png" alt="Publish Subject"/><figcaption>Publish Subject</figcaption></figure>
<!-- /wp:image -->

<!-- wp:separator -->
<hr class="wp-block-separator"/>
<!-- /wp:separator -->

<!-- wp:heading {"level":4} -->
<h4>2. Filtering sink</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Now we need to filter the items entering our <strong>sink. </strong>We use <strong>distinct </strong>to skip the data events if they are equal to the previous data event.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>The returned stream provides the same events as this stream, except that it never provides two consecutive data events that are equal.</p>
<!-- /wp:paragraph -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p><a href="https://rxmarbles.com/#distinct" rel="noreferrer noopener" target="_blank">Interactive description for distinct</a>.</p></blockquote>
<!-- /wp:quote -->

<!-- wp:separator -->
<hr class="wp-block-separator"/>
<!-- /wp:separator -->

<!-- wp:heading {"level":4} -->
<h4>3. Debounce&nbsp;</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>We wait for the user to stop typing for 500ms before running a search. This is achieved using <a rel="noreferrer noopener" href="https://pub.dev/documentation/rxdart/latest/rx/DebounceExtensions/debounceTime.html" target="_blank">debounce</a>.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>Stream.fromIterable([1, 2, 3, 4])  .debounceTime(Duration(seconds: 1))  .listen(print); // prints 4</code></pre>
<!-- /wp:code -->

<!-- wp:heading {"level":4} -->
<h4>4. switchMap</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Call the Github API with the given search term. If another search term is entered, switchMap will ensure the previous search is discarded.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>This can be useful when you only want the very latest state from asynchronous APIs, for example.</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>RangeStream(4, 1)  .switchMap((i) =>    TimerStream(i, Duration(minutes: i))  .listen(print); // prints 1</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Finally, we call the Github API:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>Stream&lt;SearchState> eventTyping(String term) async* {
  if (term.isEmpty) {
    yield SearchEmpty();
  } else {
    yield* Rx.fromCallable(() => api.search(term))
        .map((result) =>
           result.isEmpty ? SearchEmpty() : SearchPopulated(result))
        .startWith(SearchLoading())
        .onErrorReturn(SearchError());
  }
}</code></pre>
<!-- /wp:code -->

<!-- wp:list -->
<ul><li>where if the term is empty we emit <strong>SearchEmpty</strong> state</li><li>Otherwise, call the API, bundle the results into <strong>SearchPopulated</strong> state</li><li>In case of error, emit the <strong>SearchError</strong> state</li></ul>
<!-- /wp:list -->

<!-- wp:separator -->
<hr class="wp-block-separator"/>
<!-- /wp:separator -->

<!-- wp:heading {"level":3} -->
<h3>Update UI as per&nbsp;states</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Our bloc exposes a stream (<strong>called state</strong>). In our UI, we simply listen to this stream and react as per the state emitted</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>StreamBuilder&lt;SearchState>(
   builder: (context, model) {
      final state = model.state;
      if (state == States.loading) {
          return const _Loading();
      } else if (state == States.empty || state == States.noTerm) {
          return const _Empty();
      } else if (state == States.error) {
          return const _Error();
      } else if (state == States.populated) {
          return const _DisplayWidget();
      }
      return const _Internal();
   },
   initialData: SearchNoTerm(),
   stream: searchBloc.state,
)</code></pre>
<!-- /wp:code -->

*In case it helped :)*
<a href="https://www.buymeacoffee.com/aseemwangoo" target="_blank"><img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Pass Me A Coffee!!" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" ></a>

{% youtube 7eQgZ6QQwxs %}

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p>Hosted URL&nbsp;: <a href="https://web.flatteredwithflutter.com/#/" rel="noreferrer noopener" target="_blank">https://web.flatteredwithflutter.com/#/</a></p></blockquote>
<!-- /wp:quote -->

<!-- wp:quote -->
<blockquote class="wp-block-quote"><p><a href="https://github.com/AseemWangoo/experiments_with_web" rel="noreferrer noopener" target="_blank">Source code for Flutter Web App..</a></p></blockquote>
<!-- /wp:quote -->