<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Stephan E.G. Veenstra on Medium]]></title>
        <description><![CDATA[Stories by Stephan E.G. Veenstra on Medium]]></description>
        <link>https://medium.com/@seg.veenstra?source=rss-c489c66d9f1a------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/1*BucIJkmqkkzgvC3uwqcd8w.jpeg</url>
            <title>Stories by Stephan E.G. Veenstra on Medium</title>
            <link>https://medium.com/@seg.veenstra?source=rss-c489c66d9f1a------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Sat, 30 May 2026 07:52:21 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@seg.veenstra/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Better with bloc — part II]]></title>
            <link>https://medium.com/@seg.veenstra/better-with-bloc-part-ii-2291382bc104?source=rss-c489c66d9f1a------2</link>
            <guid isPermaLink="false">https://medium.com/p/2291382bc104</guid>
            <category><![CDATA[flutter]]></category>
            <category><![CDATA[app-development]]></category>
            <category><![CDATA[bloc]]></category>
            <category><![CDATA[dart]]></category>
            <dc:creator><![CDATA[Stephan E.G. Veenstra]]></dc:creator>
            <pubDate>Sat, 02 Sep 2023 21:24:26 GMT</pubDate>
            <atom:updated>2023-09-02T21:24:26.110Z</atom:updated>
            <content:encoded><![CDATA[<h3>Better with bloc — part II</h3><p>In this article I continue explaining my ways of using bloc for state management in Flutter. Just as before, I will be writing based on my personal preferences and coding style.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1000/0*_EioH6MrdGOzwPKi" /></figure><p>🚨 <strong>This article will assume you have read the </strong><a href="https://medium.com/@seg.veenstra/better-with-bloc-429db28352df"><strong>first article</strong></a><strong>.</strong></p><h3>Providing blocs</h3><p>In the <a href="https://medium.com/@seg.veenstra/better-with-bloc-429db28352df">previous article</a> I explained that I’ve made a rule when it comes to consuming blocs. To recap, only <em>destination widgets</em> are allowed to consume blocs.</p><p>For providing blocs, I have a similar rule. Blocs are provided only in the following two locations:</p><h4>Destination Widgets</h4><p>While destination widgets can consume blocs, they are also allowed to provide other blocs. So why would we want to do this?</p><p>Let’s say we are creating a questionnaire app where users can answer different types of questions, like free-text, multiple-choice, rating, etc.</p><p>Next, imagine we have the following destination in our app: /questionnaire/&lt;questionnaire-id&gt;/&lt;question-number&gt;.</p><p>The first part tells us we’re doing a questionnaire, the second part tells us which one, and the last part tells us which question we’re at. So when we’re on page /questionnaire/12/4, it means we’re at question 4 of questionnaire 12.</p><p>This page shows us a question, so we’re on the QuestionPage. To back up this page, we need a bloc, which we would call QuestionBloc (or QuestionCubit if you use cubits), which holds QuestionState.</p><p>QuestionState will have the following familiar <em>subtypes</em>, like QuestionInitial, QuestionLoading, QuestionLoaded, and QuestionError. When the state is QuestionLoaded, it will hold the Question object, which in turn can also be one of few subtypes like, FreeTextQuestion, MultipleChoiceQuestion, RatingQuestion, etc.</p><p>The QuestionPage could look something like this:</p><pre>Widget build(BuildContext context) {<br>  return BlocBuilder&lt;QuestionBloc, QuestionState&gt;(<br>    builder(context, state) =&gt; switch(state) {<br>      QuestionInitial() =&gt; _Loading(),<br>      QuestionLoading() =&gt; _loading(),<br>      QuestionLoaded(:final question) =&gt; _Loaded(question),<br>      QuestionError(:final error) =&gt; _Error(error),<br>    }<br>  );<br>}</pre><p>The fact that our loaded object (Question) can be all these different kinds of subtypes, is what would make handling this in the QuestionPage and QuestionBloc complex and messy. There would be a lot of logic just for checking the current type of the question on many occasions, like when showing the actual question or answering a question.</p><p>Instead, what we can do, is simply delegate the responsibility to question-specific blocs. By creating a bloc for each type of question, we eliminate the need for type checks, because we only have to do that once at the point of delegation.</p><p>So now we basically turn the QuestionPage into a wrapper page, whose only responsibility is loading the question, and then delegate is to the correct question-specific bloc, like so:</p><pre>Widget build(BuildContext context) {<br>  return BlocBuilder&lt;QuestionBloc, QuestionState&gt;(<br>    builder(context, state) =&gt; switch(state) {<br>      QuestionInitial() =&gt; _Loading(),<br>      QuestionLoading() =&gt; _loading(),<br>      QuestionLoaded(:final question) =&gt; switch(question){<br>        FreeTextQuestion() =&gt; BlocProvider&lt;FreeTextQuestionBloc&gt;(<br>          create: (context) =&gt; serviceLocator.create(),<br>          child: FreeTextQuestionPage(),<br>        ),<br>        MultipleChoiceQuestion() =&gt; BlocProvider&lt;MultipleChoiceQuestionBloc&gt;(<br>          create: (context) =&gt; serviceLocator.create(),<br>          child: MultipleChoiceQuestionPage(),<br>        ),<br>        RatingQuestion() =&gt; BlocProvider&lt;RatingQuestionBloc&gt;(<br>          create: (context) =&gt; serviceLocator.create(),<br>          child: RatingQuestionPage(),<br>        ),<br>        // etc...<br>      },<br>      QuestionError(:final error) =&gt; _Error(error),<br>    }<br>  );<br>}</pre><p>And thus, we end up with multiple simple-to-deal-with blocs, instead of a behemoth class that grows with every new type of question.</p><h4>Router</h4><p>The other location where I provide blocs is in my <em>router </em>configuration.</p><p>For my apps I’m using the <a href="https://pub.dev/packages/go_router">go_router</a> package. I’m not going to explain here how go_router works, but I will show you how and why I started providing blocs in it’s configuration.</p><p>Take a look at this example router configuration for a todo app:</p><pre>final todoRouter = GoRouter(<br>  routes: [<br>    // This is the root route which shows the list of Todo&#39;s<br>    GoRoute(<br>      path: &#39;/&#39;,<br>      builder: (context, state) =&gt; BlocProvider&lt;TodoListBloc&gt;(<br>        create: (context) =&gt; serviceLocator.get(),<br>        child: TodoListPage(),<br>      ),<br>      routes: [<br>        // This is the first sub-route, which shows an empty detail page.<br>        // This will be shown when the user presses the + button.<br>        GoRoute(<br>          path: &#39;new&#39;,<br>          // Here we simply provide a new instance of the TodoDetailBloc.<br>          builder: (context, state) =&gt; BlocProvider&lt;TodoDetailBloc&gt;(<br>            create: (context) =&gt; serviceLocator.get(),<br>            child: TodoDetailPage(),<br>          ),<br>        ),<br>        // This is the second sub-route, which shows a selected todo.<br>        // This will be shown when the user presses an existing todo.<br>        GoRoute(<br>          path: &#39;:id&#39;,<br>          // Here we also create a new instance of the TodoDetailBloc,<br>          // but we will also call the `load` function on it with the &#39;id&#39;<br>          // that has been passed.<br>          builder: (context, state) =&gt; BlocProvider&lt;TodoDetailBloc&gt;(<br>            create: (context) =&gt; serviceLocator.get(state.)..load(<br>              id: state.pathParameters[&#39;id&#39;],<br>            ),<br>            child: TodoDetailPage(),<br>          ),<br>        ),<br>      ],<br>    ),<br>  ],<br>);</pre><p>As you can see, every route will first provide the bloc to be used, calling functions when necessary. When I provide the blocs like this, there is no need for me to add any parameters or logic to the widgets like the TodoListPage and TodoDetailPage. They now only need to consume whatever bloc is provided above them in the widget tree.</p><p>Just like with the example earlier with the destination widget, the router is responsible for delegating, thus providing the correct bloc and page.</p><h3>Updating state</h3><p>When managing state, you will often update and transition state objects. These <em>mutations</em> often come with a set of <em>rules</em>. Like when we’re in the initial state of the bloc, the only next possible state can be loading or when we’re in the loading state, the only possible follow-up states are either loaded or error.</p><p>We can use our knowledge of these possible mutations and create specific mutation functions. When I want to create these kind of functions, I will do so by creating extensions for every subtype.</p><p>Let’s take a look at an example for the initial state from a login page:</p><pre>// LoginInitial means the user is filling in the form (username + password).<br>extension LoginInitialExt on LoginInitial {<br><br>  // The user has updated the username field<br>  LoginInitial applyUsername(String username) {<br>    return copyWith(username: username);<br>  }<br><br>  // The user has updated the password field<br>  LoginInitial applyPassword(String password) {<br>    return copyWith(password: password);<br>  }<br><br>  // The user has submitted the login form<br>  LoginLoading applySubmit() {<br>    return LoginLoading(<br>      username: username,<br>    );<br>  }<br>}</pre><p>Here the functions clearly tell you what the LoginInitial state is capable off. It can update itself when form-fields are updated, or transition into LoginLoading when the form has been submitted.</p><p>The bloc or cubit can now call these functions instead of having to contain all the logic for all the different states and transitions, like this:</p><pre>// Called every time the username field is updated<br>void updateUsername(String username) {<br>  final currentState = state;<br>  switch(currentState) {<br>    // When the currentState is LoginInitial, we can update the state.<br>    case LoginInitial():<br>      final newState = currentState.applyUsername(username);<br>      emit(newState);<br>    // Also, when login failed, we should be able to enter a new username.<br>    case LoginError():<br>      final newState = currentState.applyUsername(username);<br>      emit(newState);<br>    // Any other state should not call this, so we can ignore it.<br>    default:<br>  }<br>}</pre><p>We have separated the concerns for updating the state into two parts:</p><ol><li>The bloc/cubit is responsible for determining in which state which mutation can take place. It will then call the mutation function.</li><li>The extensions contain the implementation of the mutation functions which are responsible for the actual mutation of the state.</li></ol><h3>Creating blocs</h3><p>You might have seen earlier in this article, that I’m using a service locator to instantiate blocs/cubits. I use a package for this called <a href="https://pub.dev/packages/get_it">get_it</a>, but you can also use an alternative like <a href="https://pub.dev/packages/ioc_container">ioc_container</a>, or write something yourself.</p><p>In the main.dart, I will setup get_it so that it can return a new instance of a bloc when requested:</p><pre>// Make GetIt globally available<br>final serviceLocator = GetIt.instance;<br><br>void main() {<br>  // From repositories we only have a single instance which will be<br>  // shared across blocs.<br>  final todoRepo = TodoRepo();<br><br>  // Register factories for the blocs<br>  serviceLocator.registerFactory&lt;TodoListBloc&gt;(<br>    () =&gt; TodoListBloc(todoRepo: todoRepo),<br>  );<br>  serviceLocator.registerFactory&lt;TodoDeetailBloc&gt;(<br>    () =&gt; TodoDetailBloc(todoRepo: todoRepo),<br>  );<br>}</pre><p>And now we can simply request a new instance by using:</p><pre>serviceLocator.get&lt;TodoDetailBloc&gt;();</pre><p>…in BlocProviders.</p><h3>Syncing blocs</h3><p>Sometimes your app has pages that need to share information. Like when you have an app with product which can be favorited. Both the ProductListViewPage as the FavoriteProductsPage should be updated whenever a product is (un)favorited.</p><p>The first thing I want to mention about this is, that blocs should NOT be connected to one-another, as per documentation:</p><blockquote>Because blocs expose streams, it may be tempting to make a bloc which listens to another bloc. You should not do this.<br>- <a href="https://bloclibrary.dev/#/architecture?id=bloc-to-bloc-communication">docs</a></blockquote><p>Blocs and cubits should only be updated by the repositories they depend on, or events/functions triggered from the UI layer.</p><p>Coming back to the product app example, where both the ProductListViewPage as the FavoriteProductsPage rely on the favorited products. The ProductListViewPage should show a filled heart for product that already have been favorited and FavoriteProductsPage should just show all the favorited products.</p><p>This means both the ProductListViewCubit and the FavoriteProductsCubit will have a dependency on the (same) FavoritesRepository, which ideally would expose a stream of the currently favorited products.</p><p>Both cubits can subscribe to this stream and trigger (private) functions to update the state. An example:</p><pre>class FavoriteProductsCubit extends Cubit&lt;FavoriteProductsState&gt; {<br>  FavoriteProductsCubit(this._favoritesRepo) : super(FavoriteProductsState.initial()) {<br>    // We can put the state in loading because we&#39;re starting to listen for<br>    // favorites right away.<br>    emit(FavoriteProductsState.loading());<br>    // Here we subscribe on the favorites stream from the FavoritesRepository<br>    // and pass in a private function to handle the updates.<br>    _favoritesRepoSubscription = _favoritesRepo.favorites.listen(_onFavoritesUpdated);<br>  }<br><br>  final FavoritesRepository _favoritesRepo;<br>  final StreamSubscription? _favoritesRepoSubscription;<br><br>  // The function that is called every time the list of favorites changes.<br>  void _onFavoritesUpdated(List&lt;Favorite&gt; favorites) {<br>    final currentState = state;<br>    switch(currenState) {<br>      case FavoriteProductsLoading():<br>        // This mutation will transition the state from FavoriteProductsLoading<br>        // to FavoriteProductsLoaded.<br>        // The actual mutating is delegated to mutation functions as explained<br>        // earlier.<br>        final newState = currentState.applyFavorites(favorites);<br>        emit(newState);<br><br>      // If the favorites are updated and we were already looking at the list<br>      // we still want to update the list when it changes.<br>      case FavoriteProductsLoaded():<br>        final newState = currentState.applyFavorites(favorites);<br>        emit(newState);<br>      default:<br>    }<br>  }<br><br>  // Favorite the given product. Only available when state is loaded.<br>  void favoriteProduct(Product product) {<br>    final currentState = state;<br>    switch(currenState) {<br>      case FavoriteProductsLoaded():<br>        // We call the repository to update the &#39;data&#39;.<br>        _favoritesRepo.favorite(product);<br>        // We don&#39;t have to update the state here, because the update of the<br>        // repository will trigger the listener, which will cause the state<br>        // be updated.<br>        // This way we keep a nice unidirectional data-flow.<br>      default:<br>    }<br>  }<br>  <br>  // Unfavorite the given product. Only available when state is loaded.<br>  void unfavoriteProduct(Product product) {<br>    final currentState = state;<br>    switch(currenState) {<br>      case FavoriteProductsLoaded():    <br>        _favoritesRepo.unfavorite(product);<br>      default:<br>    }<br>  }<br><br>  void close() {<br>    // Always close the subscriptions!<br>    _favoritesRepoSubscription?.cancel();<br>    super.close();<br>  }<br>}</pre><p>In this example for the FavoriteProductsCubit a few interesting things are happening. We rely on the FavoritesRepository for the latest favorites data. So, when an favoriteProduct/unfavoriteProduct function is called, we do not need to update the state in the function itself. This is because our action will cause the FavoritesRepository to be updated. This will update the stream that we are listening to, which in it’s turn will call the _onFavoritesUpdated listener function resulting in the new, up-to-date, state.</p><p>Because we’re putting the FavoritesRepository in charge of the data, it becomes a <em>single source of truth </em>and both cubits can stay in sync without having to rely on one-another!</p><h3>Conclusion</h3><p>In this article I’ve given some more advanced ways in how I use bloc in my applications. We’ve covered:</p><ol><li>The way that I’m providing blocs in <em>destination widgets</em> and via go_router.</li><li>The way I’m updating state with the use of <em>mutation functions</em>, and how they result in a nice way to separate concerns.</li><li>How I create blocs with the help of get_it.</li><li>And how I keep my blocs in sync by relying on repositories to be the single source of truth.</li></ol><p>Again, I hope that these articles are interesting or even helpful. If you do like this format, where I write about how I deal with real-life scenarios and use-cases, please leave some claps 👏 (the more the better 😉). If you have any questions, or suggestions, leave a comment below👇! Thank you! 🙏</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=2291382bc104" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Better with bloc]]></title>
            <link>https://medium.com/@seg.veenstra/better-with-bloc-429db28352df?source=rss-c489c66d9f1a------2</link>
            <guid isPermaLink="false">https://medium.com/p/429db28352df</guid>
            <category><![CDATA[flutter]]></category>
            <category><![CDATA[app-development]]></category>
            <category><![CDATA[bloc]]></category>
            <category><![CDATA[dart]]></category>
            <category><![CDATA[state-management]]></category>
            <dc:creator><![CDATA[Stephan E.G. Veenstra]]></dc:creator>
            <pubDate>Tue, 22 Aug 2023 20:28:43 GMT</pubDate>
            <atom:updated>2023-09-04T07:17:20.331Z</atom:updated>
            <content:encoded><![CDATA[<p>In this article I will show you how I use bloc state management in my Flutter projects. The approaches mentioned in this article are based on my personal preferences and coding style.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*y29q_U87GZIMYGpObpOcPQ.jpeg" /></figure><p>🚨 <strong>This article will assume you have at least some basic knowledge of the </strong><a href="https://pub.dev/packages/flutter_bloc"><strong>flutter_bloc package</strong></a><strong>. For learning on how to get started with flutter_bloc, please refer to the docs.</strong></p><p>💡 <em>In this article I will mostly speak of blocs, but these can easily be substituted by cubits.</em></p><h3>Bloc feat. Freezed</h3><p><a href="https://pub.dev/packages/freezed">Freezed</a> is a package that goes very well with flutter_bloc. If you don’t know what freezed is, you are missing out!</p><p>️️️️💡 <em>Freezed basically allows you to write super-charged (data) classes. All you have to do is define the properties for your class and freezed can generate things like </em><em>toString, </em><em>hashcode, </em><em>operator == and </em><em>copyWith methods for you.</em></p><p>The bloc documentation describes <a href="https://bloclibrary.dev/#/blocnamingconventions?id=subclasses-1">two</a> ways of creating state objects. The first one is by using subclasses, where every subclass represents a state the state object can be in.</p><pre>sealed class CounterState {}<br>final class CounterInitial extends CounterState {}<br>final class CounterLoading extends CounterState {}<br>final class CounterLoaded extends CounterState {<br>  required int count,<br>}<br>final class CounterError extends CounterState {}</pre><p>The second is by using a single class where the different states are represented by the status field.</p><pre>enum CounterStatus { initial, loading, loaded, error }<br>final class CounterState {<br>  const CounterState({this.status = CounterStatus.initial});<br>  final CounterStatus status;<br>  final int? count;<br>}</pre><p>I prefer the first option. Using subclasses allows me to be mutual exclusive, so I never have to worry if a field of the state can or shouldn’t be accessed. In the single class example, the count field will always be accessible, while it probably only should be read when the state is loaded.</p><p>Unfortunately, creating that many subclasses by hand can be quite cumbersome. Specially when they get (and share) a lot of fields.</p><p>Lucky for us, there are freezed <em>union types</em>!</p><h4>Union types</h4><p>One of the best things of freezed is the support for <em>union types</em>. You can think of a <em>union</em> as a class that can have different representations, just like our state object.</p><p>I can create the same state object with freezed, like this:</p><pre>@freezed<br>sealed class CounterState with _$CounterState {<br>  const factory CounterState.initial() = CounterInitial;<br>  const factory CounterState.loading() = CounterLoading;<br>  const factory CounterState.Loaded({<br>    required int count,<br>  }) = CounterLoaded;<br>  const factory CounterState.error() = CounterError;<br>}</pre><p>It looks very similar to the original example, but under the hood, freezed will generate code that makes our lives so much easier.</p><h4>Using our state object</h4><p>Now that we have a nice state object to work with, let’s use it. We are going to implement an <em>increment</em> method for our CounterCubit:</p><pre>void increment() {<br>  // We have to store the current state into a local variable in<br>  // order for Dart to be able to downcast it.<br>  final currentState = state;<br>  switch(currentState) {<br>    // We can only increment when the state is CounterLoaded<br>    case CounterLoaded(:final count):<br>      emit(currentState.copyWith(count: count + 1));<br>    // Anything else will just be ignored<br>    default:<br>  }<br>}</pre><p>As you can see, we’re able to use the generated .copyWith method to easily turn the current state into the new state.</p><p>️️<em> </em>️ 💡 <em>In this example I’m using the new </em><a href="https://dart.dev/language/patterns"><em>Dart pattern matching</em></a><em> functionality that came with Dart 3. Freezed is also able to generate </em><a href="https://pub.dev/packages/freezed#when"><em>.map/.when</em></a><em> methods for us that can basically do the same. While I prefer the </em><em>map/when syntax, I’m not using it since it’s been discouraged by the author.</em></p><p>Still, freezed just made our lives a bit… cooler 😎.</p><h3>Custom listeners</h3><p>If you ever had to fire off one-off actions based on state changes, like navigating, or showing a snackbar, you will most likely have used a BlocListener.</p><p>But when you have to listen to a bunch of different state changes, the code can become quite cluttered.</p><p>Let’s take a look at the following example:</p><pre>Widget build(BuildContext context) {<br>  return MultiBlocListener(<br>    listeners: [<br>      // 1. When the user has successfully logged in, navigate to home<br>      BlocListener&lt;LoginBloc, LoginState&gt;(<br>        // Only trigger when state transitions into loginSuccess<br>        listenWhen: (previousState, state) =&gt; <br>            previousState is! LoginSuccess &amp;&amp; state is LoginSuccess,<br>        listener: (context, state) =&gt; context.go(&#39;/home/${state.user.id}&#39;),<br>      ),<br>      // 2. Show a dialog when the user account needs to be verified<br>      BlocListener&lt;LoginBloc, LoginState&gt;(<br>        // Only trigger when state transitions into loginSuccessUnverified<br>        listenWhen: (previousState, state) =&gt; <br>          previousState is! LoginSuccessUnverified &amp;&amp; state is LoginSuccessUnverified,<br>        listener: (context, state) =&gt; context.showVerificationDialog(),<br>      ),<br>    ],<br>    child: BlocBuilder&lt;LoginBloc, LoginState&gt;(<br>      builder: (context, state) {<br>        // UI CODE HERE<br>      },<br>    ),<br>  );<br>}</pre><p>As you can see, there is quite a bit of logic in our UI file and it’s making quite a mess. I also felt the need to add comments to make it more clear to what each listener does.</p><p>But, we can easily make this better bij creating our own listeners by extending BlocListener!</p><pre>class LoginSuccessListener extends BlocListener&lt;LoginBloc, LoginState&gt; {<br>  LoginSuccessListener(<br>    void Function(BuildContext context, LoginState state) listener,<br>  {<br>      super.child, <br>  }) : super(<br>    buildWhen: (previousState, state) =&gt; <br>        previousState is! LoginSuccess &amp;&amp; state is LoginSuccess,<br>    listener: listener,<br>  );<br>}</pre><p>Doing this has several benefits:</p><ol><li>The name of the listener tells you what it does.</li><li>The buildWhen logic is nicely tucked away.</li><li>The new listener is easily reusable.</li></ol><p>If we do the same thing for the other listener, we can clean up our UI like so:</p><pre>Widget build(BuildContext context) {<br>  return MultiBlocListener(<br>    listeners: [<br>      LoginSuccessListener(<br>          (context, state) =&gt; context.go(&#39;/home/${state.user.id}&#39;),<br>      LoginSuccessUnverifiedListener(<br>          (context) =&gt; context.showVerificationDialog()),<br>    ],<br>    child: BlocBuilder&lt;LoginBloc, LoginState&gt;(<br>      builder: (context, state) {<br>        // UI CODE HERE<br>      },<br>    ),<br>  );<br>}</pre><p>That’s a lot better now, isn’t it?</p><h3>Consuming blocs</h3><p>Even though blocs are a crucial part of my applications, I don’t want them to be mixed in with all the UI code. Therefor I have one very strict rule:</p><blockquote>Only destination widgets should deal with blocs!</blockquote><p>Widgets that are the root of a page, screen, dialog, bottomsheet, etc is what I call a <em>destination widget</em>. These are widgets that you use in your <em>router</em>, or your <em>navigation</em> methods, for example:</p><pre>Navigator.push(context, MaterialPageRoute(builder: (context) =&gt; HomePage()));</pre><p>Here HomePage can be considered a destination widget.</p><p>So, only destination widgets deal with blocs. They will orchestrate the content on the screen.</p><p>Most of the time I will split up a destination widget into several private widgets that can represent each state. If the file is getting too big for your taste, you could break the file up in their own (part) files.</p><p>Let’s take a look at a full example.</p><pre>class TodoListPage extends StatelessWidget {<br><br>  @override<br>  Widget build(BuildContext context) {<br>    // We start with the listeners for this page.<br>    return MultiBlocListener(<br>      listeners: [<br>        TodoRemovedListener((context) =&gt; context.showTodoRemovedSnackbar()),<br>        TodoAddedListener((context) =&gt; context.showTodoAddedSnackbar()),<br>      ],<br>      // Then we add the builder.<br>      child: BlocBuilder&lt;TodoListBloc, TodoListState&gt;(<br>        builder: (context, state) =&gt; switch(state) {<br>          <br>          // Often initial and loading can use the same widget.<br>          TodoListInitial() =&gt; _Loading(),<br>          TodoListLoading() =&gt; _Loading(),<br><br>          // When the state is loaded, we want to show the todos.<br>          TodoListLoaded(:final todos) =&gt; _Loaded(todos),<br><br>          // When we are refreshing, it might be nice to keep showing the<br>          // current todos, but we also tell the widget to indicate refreshing.<br>          TodoListRefreshing(:final todos) =&gt; _Loaded(todos, isRefreshing: true),<br>          <br>          // If loading went wrong, we can show an error.<br>          TodoListError(:final error) =&gt; _Error(error),<br>        }<br>      ),<br>    ),<br>  }<br>}<br><br>class _Loading extends StatelessWidget {<br>  @override<br>  Widget build(BuildContext context) {<br>     // Show loading screen<br>  }<br>}<br><br>class _Loaded extends StatelessWidget {<br>  _Loaded(this.todos, {this.isRefreshing = false});<br><br>  final List&lt;Todo&gt; todos;<br>  final bool isRefreshing;<br><br>  @override<br>  Widget build(BuildContext context) {<br>     // Show Todo List and optional a refreshing indicator<br>     // If the todos are empty, maybe even show an empty list message<br>  }<br>}<br><br>class _Error extends StatelessWidget {<br>  _Error(this.error);<br><br>  final Error error;<br><br>  @override<br>  Widget build(BuildContext context) {<br>     // Show error screen<br>  }<br>}</pre><p>I think this looks pretty tight! 🔥</p><p>💡 <em>The reason I limit the amount of widgets that may access blocs, is because this way, I know exactly where things can go wrong. It also allows for all the other, non-destination, widgets to be as ‘dumb’ as possible.</em></p><h3>Conclusion</h3><p>In this article I’ve shown you how I use bloc in my applications.</p><ul><li>First I talked about the freezed package, and how we can benefit from it in combination with bloc.</li><li>Second, I showed you what I do to move BlocListener logic out of the UI by creating custom BlocListeners with more descriptive names.</li><li>Lastly, I explained which widgets, that I call destination widgets, I allow to have access to blocs, and why.</li></ul><p>I hope you found this article useful/interesting. If you like more of these please let me know by clapping 👏. If you have specific things you’d like to see, please leave a comment👇! Thanks! 🙏</p><p><a href="https://medium.com/@seg.veenstra/better-with-bloc-part-ii-2291382bc104">Continue reading: Part II</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=429db28352df" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Extending the Flutter Theme.]]></title>
            <link>https://medium.com/pinch-nl/extending-the-flutter-theme-48799ebe6c5d?source=rss-c489c66d9f1a------2</link>
            <guid isPermaLink="false">https://medium.com/p/48799ebe6c5d</guid>
            <category><![CDATA[flutter-app-development]]></category>
            <category><![CDATA[flutter-ui]]></category>
            <category><![CDATA[flutter]]></category>
            <category><![CDATA[flutter-theme]]></category>
            <dc:creator><![CDATA[Stephan E.G. Veenstra]]></dc:creator>
            <pubDate>Sat, 08 Jan 2022 21:51:48 GMT</pubDate>
            <atom:updated>2022-02-22T13:31:41.414Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*9l_qIepNmR8Flg-s" /><figcaption>Photo by <a href="https://unsplash.com/@alicegrace?utm_source=medium&amp;utm_medium=referral">Alice Dietrich</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>In Flutter’s ThemeData you’ll find all the things you need to style you app based on the <a href="https://material.io/">material design guidelines</a>.<br>There are color schemes, text themes and even styling for material components like the <a href="https://material.io/components/buttons-floating-action-button">Floating Action Button</a>.</p><p>But what if you want to define your own styling, for your custom Widget for example?<br>If you, just like me, have tried to do this, you most likely found out that this is not that easy to do.</p><p>But I found a way to do this which I would like to share with you!</p><h3>Solution goals</h3><p>The solution that I wanted to use should have the following three properties:</p><p><strong>A. One theme to rule them all.</strong><br>The goal is to have just one ‘theme’ object so you won’t end up with themes that get out of sync.</p><p><strong>B. Theme should be context based</strong><br>I want to have the theme come from context so I can ‘scope’ the theme to certain parts of the app as I please. This also means that when the context changes, I can adapt to it.</p><p><strong>C. Our solution should be backwards compatible with the old theme.</strong><br>We don’t want the default behavior to break and mess with the material components Flutter provides.</p><h3>Solutions used by the community</h3><p>I’ve looked and asked around on the internet to see which solutions the community is using. Two solutions came up the most:</p><h4><strong>Using extension methods.</strong></h4><p>I think this is the one I saw the most. We could use extension methods to add properties to ThemeData. The nice part is, that we don’t have to change the way we access the Theme. We can still use the normal Theme.of(context).<br>But, the drawback is that these properties are NOT instance properties which means that it’s not as dynamic.<br>This violates <strong>goal B</strong>.</p><pre>extension ExtendedThemeData on ThemeData {<br>  Color get myCustomColor =&gt; Colors.yellow;<br>}</pre><h4><strong>Create another Theme-like object.</strong></h4><p>We could also just create our own separate object which would hold our custom properties. We could use an InheritedWidget (or a package like provider/riverpod) to access it from the context.<br>But now we have to keep track, and if needed, switch between multiple theme related objects, which violates <strong>goal A</strong>.</p><pre>class CustomTheme {<br>  final Color myCustomColor;</pre><pre>  const CustomTheme({required this.myCustomColor});<br>}</pre><p>Using extension methods was just not going to cut it. The only way this could become more dynamic would mean it should probably need some logic per property and I want the theme data to be as static as possible.</p><p>However, using a separate object does allow us to be dynamic. All I had to do was find a way to keep our custom theme in sync with the default theme.</p><h3>My solution</h3><p>So I liked the approach of creating a separate object, but hated the idea of having to manage multiple types of theme objects.<br>Luckily we can fix it, and all we have to do is create three simple classes…</p><h4>MyThemeData</h4><p>We start off by defining our own ThemeData class. You can name it whatever you like. It could be something generic, like AppThemeData, or something very app specific like WeatherAppThemeData. I recommend to end the name with ‘Data’. For this example I will call this class MyThemeData.</p><p>This class will hold all of your custom properties, like colors, spacings, or styling for complete widgets. One important thing is that it also includes a field to hold the default ThemeData. Notice that the themeData field is late and NOT final. This makes it easier to swap out later on to get the right behavior when accessing the default ThemeData.<br>Alternatively you could create a copyWith method (or use the <a href="https://pub.dev/packages/copy_with_extension">copy_with_annotation</a> package).</p><pre>class MyThemeData {<br>  late ThemeData themeData; // important!<br>  final Color myCustomColor;<br>  final CustomWidgetData customWidget;</pre><pre>  MyThemeData({<br>    required this.themeData,<br>    required this.myCustomColor,<br>    required this.customWidget,<br>  });<br>}</pre><pre>class CustomWidgetData {<br>  final Color backgroundColor;<br>  final BoxShape shape;</pre><pre>  const CustomWidgetData({<br>    required this.backgroundColor,<br>    required this.shape,<br>});</pre><p>That’s it, just add all the properties you need.</p><h4>InheritedMyTheme</h4><p>We need a way to provide our newMyThemeData objects. We are going to use an <a href="https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html">InheritedWidget</a> for this.</p><p>Because I named my ThemeData object MyThemeData, I will name this widget InheritedMyTheme. Again, it’s just a very simple, but powerful, class.<br>All it has to do is provide the MyThemeData that we want the underlying branches of the <em>widget tree</em> to use.</p><pre>class InheritedMyTheme extends InheritedWidget {<br>  final MyThemeData data;</pre><pre>  const InheritedMyTheme({<br>    required this.data,<br>    required Widget child,<br>    Key? key,<br>  }):super(key: key, child: child,);</pre><pre>  @override<br>  bool updateShouldNotify(covariant InheritedWidget oldWidget) =&gt;     <br>      oldWidget != this;<br>}</pre><h4>MyTheme</h4><p>Finally, we are going to create the widget that we need to actually use our custom theme in our app.</p><p>It will be a <a href="https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html">StatelessWidget</a> that we can put anywhere in the <em>widget tree </em>where we want to use our new custom theme. We will support both light and dark mode based on the OS configuration.</p><p>Another very important task of this widget is providing a normal Theme widget so it remains compatible, and so both theme objects stay in sync.</p><p>We will also add a convenient method so we can simple access our theme with MyTheme.of(context). You’ll notice that we are getting the themeData as well.<br>This is needed to make sure that Theme.of(context) and MyTheme.of(context).themeData will return the same values.</p><pre>class MyTheme extends StatelessWidget {<br>  final MyThemeData light;<br>  final MyThemeData dark;<br>  final Widget child;</pre><pre>  const MyTheme({<br>    required this.light,<br>    required this.dark,<br>    required this.child,<br>    Key? key,<br>  }) : super(key: key);</pre><pre>  @override<br>  Widget build(BuildContext context) {<br>    final brightness = MediaQuery.of(context).platformBrightness;<br>    final data = brightness == Brightness.light ? light : dark;<br>     <br>    return InheritedMyTheme(<br>      data: data,<br>      child: Theme(<br>        data: data.themeData,<br>        child: child,<br>      ),<br>    );<br>  }</pre><pre>  static MyThemeData of(BuildContext context){<br>    final theme = Theme.of(context);</pre><pre>    return context<br>        .dependOnInheritedWidgetOfExactType&lt;InheritedMyTheme&gt;()!<br>        .data..themeData = theme;<br>  }<br>}</pre><p>This completes the solution. Let’s use it!</p><h3>Usage</h3><p>We’ve created multiple classes. Let’s see how we use them!</p><h4>Create the themes</h4><p>First we will create a light and a dark theme. To keep it simple I will just use ThemeData.light() and ThemeData.dark(). But since these are just the normal ThemeData objects you can configure them however you want.</p><pre>final myLightTheme = MyThemeData(<br>  themeData: ThemeData.light(),<br>  myCustomColor: Colors.lightBlue,<br>  customWidget: CustomWidgetData(<br>    backgroundColor: Colors.lightGreen,<br>    shape: BoxShape.circle,<br>  ),<br>);</pre><pre>final myDarkTheme = MyThemeData(<br>  themeData: ThemeData.dark(),<br>  myCustomColor: Colors.blue,<br>  customWidget: CustomWidgetData(<br>    backgroundColor: Colors.green,<br>    shape: BoxShape.circle,<br>  ),<br>);</pre><h4>Using the themes</h4><p>Usually you would provide your ‘main’ theme in the theme and darkTheme fields of theMaterialApp. But since our new theme is <strong>not</strong> a subtype of ThemeData, we can’t do that here.</p><p>We will use the builder field of MaterialApp which will allow us to put our MyTheme widget just above the Navigator, which is exactly what we want because then our theme gets used by all the pages we navigate to.</p><pre>class MyApp extends StatelessWidget {<br>  const MyApp({Key? key}) : super(key: key);<br>  <br>  @override  <br>  Widget build(BuildContext context) {<br>    return MaterialApp(<br>      home: const Content(),<br>      builder: (context, child) =&gt; MyTheme(<br>        light: myLightTheme,<br>        dark: myDarkTheme,<br>        child: child ?? ErrorWidget(&#39;Child needed!&#39;),       <br>      ),<br>    );<br>  }<br>}</pre><p>That’s all! We can now access our theme anywhere in the app, simply by calling MyTheme.of(context).</p><p>Remember the CustomWidgetData form earlier?<br>Let’s see how our CustomWidget could use our theme!</p><pre>class CustomWidget extends StatelessWidget {<br>  <br>  @override<br>  Widget build(BuildContext context) {<br>    final myTheme = MyTheme.of(context);</pre><pre>    return Container(<br>      decoration: BoxDecoration(<br>        color: myTheme.customWidget.backgroundColor,<br>        shape: myTheme.customWidget.shape,<br>      ),<br>      child: Text(<br>        &#39;I use the default ThemeData&#39;,<br>        style: myTheme.themeData.textTheme.headline1,<br>      ),<br>    );<br>  }<br>}</pre><p>And that’s a wrap! We’re done.</p><h4>Example</h4><p>You can checkout this basic example I made on <a href="https://dartpad.dev/?id=47ebb83cf85f9efbdbe47424a8815359">dartpad</a>.</p><h3>Conclusion</h3><p>We’ve created our own solution to extend the theming in Flutter.<br>For this we’ve created a custom ThemeData class, an InheritedWidget and a StatelessWidget that will help us pass around our custom theme through context to stay dynamic.</p><p>By wrapping and providing the existing ThemeData we are able to keep both new and old themes in sync, which makes switching themes easy and concise, but also makes it compatible with the existing Theme.</p><h4>Extra</h4><p>Even though the amount of code we’ve written isn’t that much, it might get a bit annoying to write it for every project. There are also a few things that could be improved upon, but would require even more boilerplate code.</p><p>Therefor I started working on a package that I plan to release soon that would hopefully make things a bit easier.<br>So stay tuned!</p><p><strong>Update (8 February 2022)</strong></p><p>I’ve create a package called <a href="https://pub.dev/packages/ext_theme">ext_theme </a>on pub.dev that implements my solution. It will generate all the necessary classes for you based on some simple configuration.<br>I haven’t spend much time yet cleaning it up, but it does work.<br>I’m already using it in my own project.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*wTR8WilwKm-nl-dQZqt4Kg.jpeg" /></figure><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*AIbfPFzn19DtloFqMMKOLQ.jpeg" /><figcaption>Separate light and dark theme for Tiles</figcaption></figure><p><a href="https://github.com/SEGVeenstra/ext_theme">GitHub - SEGVeenstra/ext_theme: Extended themes for Flutter</a></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=48799ebe6c5d" width="1" height="1" alt=""><hr><p><a href="https://medium.com/pinch-nl/extending-the-flutter-theme-48799ebe6c5d">Extending the Flutter Theme.</a> was originally published in <a href="https://medium.com/pinch-nl">Pinch.nl</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[I left two perfectly ‘fine’ companies within one year, here’s why.]]></title>
            <link>https://medium.com/@seg.veenstra/i-left-two-perfectly-fine-companies-within-one-year-here-s-why-64884d5cffdb?source=rss-c489c66d9f1a------2</link>
            <guid isPermaLink="false">https://medium.com/p/64884d5cffdb</guid>
            <category><![CDATA[quitting-a-job]]></category>
            <category><![CDATA[job-switch]]></category>
            <category><![CDATA[flutter]]></category>
            <category><![CDATA[follow-your-passion]]></category>
            <dc:creator><![CDATA[Stephan E.G. Veenstra]]></dc:creator>
            <pubDate>Mon, 26 Jul 2021 09:19:44 GMT</pubDate>
            <atom:updated>2021-07-26T09:19:44.827Z</atom:updated>
            <content:encoded><![CDATA[<p>Next week I will start my new job. This means I quit my current job, which I only had since the beginning of this year (seven months ago).<br>This also means I’ve quit two jobs within less than a year.<br>Both companies where ‘fine’, so why did I leave?</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*sxe1v1XDnNFTwFYV" /><figcaption>Photo by <a href="https://unsplash.com/@tar1k?utm_source=medium&amp;utm_medium=referral">Tarik Haiga</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><h3>How it al began</h3><p>My professional software development career started back in februari 2016. I had just graduated and got hired by one of the companies where I’d done my internship. It’s a great company full of great people. I had a great time.</p><p>For the first couple of years I was absorbing the knowledge and skills of my superiors. Eventually I developed my own <em>style</em> and <em>preferences</em> towards software development.</p><h4>Flutter</h4><p>At the end of 2018, I discovered Flutter, a new Cross-Platform UI Framework from Google. I fell in love with it right away and I thought it would be the perfect replacement for the framework that we were currently using.</p><p>The following years I played around with Flutter and even organized workshops for my colleagues to show them the potential of this new amazing framework.</p><p>We even did a production project with great success. I was sure I’d proven Flutter. But I was wrong…</p><h4>Mind made up</h4><p>For me it was completely clear, we needed to use Flutter. It would be better for the company as most of the developers where fett up with the current framework we used.</p><p>Half way through 2020 it became clear we would not be adopting Flutter any time soon, I realized I might had to look elsewhere if I wanted to continue working with it. Playing around with it in my spare time just wouldn’t be enough if I wanted to become and expert.</p><h3>The opportunity</h3><p>Just a few months after I had decided to start looking around for a Flutter position, I was contacted by a recruiter.<br>He was looking for Flutter developers for a company which had recently adopted Flutter and wanted to start a Flutter team.</p><h4>A tough decision</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*ilj-fGfJNXPMMXcY" /><figcaption>Photo by <a href="https://unsplash.com/@miracleday?utm_source=medium&amp;utm_medium=referral">Elena Mozhvilo</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Full time working with Flutter?! That sounded amazing!<br>But the company was 189 kilometers away, so I would work remotely. Ever since COVID-19 we’d been working a lot from home, but I couldn’t really get used to it…</p><p>I did the interview anyway.<br>It went well, I heard a lot of things I liked that had gotten me all exited.<br>After giving it some thought I realized I just had to do this.<br>Even if I had to give up an amazing company… and had to work fully remote.</p><p>As of January 2021 I would be a full time Flutter developer.</p><h4>Half a year later</h4><p>A lot had happend the first half year at my new job. I learned a lot from my new colleagues. I really liked how much attention there was for quality.<br>The amount of experience I gained exceeded my expectations.<br>I had only been in the office two times. I had gotten used to working remotely.</p><p>But there also happend a few things that I wasn’t so happy with which made me reevaluate my position. This let to me keeping my eyes open.<br>I also started missing some of the aspects of my previous job.<br>My plan was to scale down my working hours for the company and pick up freelancing again to fill that void.</p><h4>The next contestant</h4><p>While I was still reevaluating I was contacted by another recruiter. Another company that adopted Flutter was looking for more Flutter developers.</p><p>After the second interview I was sold. It was everything I wished for, and more. I would not have to partially turn to freelancing, because the reason I would turn to freelancing, would be part of this new job.<br>This would also mean I would have more time to spend with my family.<br>I simply just couldn’t let this opportunity pass by.</p><h3>So here we are</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*JgNlQa4-s9bIQkGW" /><figcaption>Photo by <a href="https://unsplash.com/@belart84?utm_source=medium&amp;utm_medium=referral">Artem Beliaikin</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>Within a span of only 7 months, I’ve left two perfectly ‘fine’ companies.</p><p>The first time was because of wanting different things. I would just not be happy I’ve I had to give up Flutter.</p><p>The second time was because the other position felt like a better fit for me.</p><p>Was it scary? Yes.<br>Would I’ve done it again, Heck yes!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=64884d5cffdb" width="1" height="1" alt="">]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Comparing objects in Dart made easy with Equatable.]]></title>
            <link>https://medium.com/pinch-nl/comparing-objects-in-dart-made-easy-with-equatable-d208e5eb9571?source=rss-c489c66d9f1a------2</link>
            <guid isPermaLink="false">https://medium.com/p/d208e5eb9571</guid>
            <category><![CDATA[equatable]]></category>
            <category><![CDATA[dart]]></category>
            <category><![CDATA[comparison]]></category>
            <category><![CDATA[equality]]></category>
            <dc:creator><![CDATA[Stephan E.G. Veenstra]]></dc:creator>
            <pubDate>Mon, 19 Jul 2021 16:53:05 GMT</pubDate>
            <atom:updated>2022-02-22T13:31:09.271Z</atom:updated>
            <content:encoded><![CDATA[<p>Sometimes we need to compare objects to see if they are equal to one another. This can easily be accomplished with the equatable package.</p><p>I took a closer look into this concept of equality when I started working on the puzzle game that I’m building. It was not the first time I’ve had to compare objects, but this time it became more crucial. Now my game heavily relies on comparing objects and by doing it the &#39;right&#39; way, it even made my code easier to read and understand.</p><p>Allow me to enlighten you.</p><h3>Same vs Equal</h3><p>So there is a difference between objects being the <em>same</em>, and objects being <em>equal</em>.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/0*PrMieOCIAMHZZvw8" /><figcaption>Photo by <a href="https://unsplash.com/@jakobnoahrosen?utm_source=medium&amp;utm_medium=referral">Jakob Rosen</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure><p>When we are talking about objects being the same, we refer to the same instance of an object. When you’re carpooling to work, you and your colleague are in the <em>same</em> car.</p><p>On the other hand, when you and your colleague both go with your own cars, they could be considered <em>equal</em>, depending on which properties you decide to compare. Like when the cars are the same make, year and color, we could consider them <em>equal</em>, right?</p><h3>Comparing objects in Dart</h3><p>The most obvious way to compare two objects is of course the == operator. This will in fact check for <em>equality</em>. To check if two objects are the <em>same</em> instance, we should use identical() .</p><blockquote>identical can act a little unexpected when working with primitives like int or String . You hardly ever need to use identical but you should be aware.</blockquote><p>Let’s checkout some examples:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/00b80c265fe186916047738494f0a20c/href">https://medium.com/media/00b80c265fe186916047738494f0a20c/href</a></iframe><p>As you probable noticed, both identical and equality checks result in the same output. So what is going on here?</p><h4>Hashcode and ==</h4><p>By default, each instance you create is unique. This explains the behavior in the example above.</p><p>When we want two instances to be considered <em>equal</em>, we must override == operator and hashcode on that class. When overriding, it’s important that the outcomes of both overrides are consistent.</p><p>When we want two objects to be considered <em>equal</em> the == operator should return true and hashcode on both objects should be returning the same int .</p><p>Let’s do this for our car example:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/20ee8caf0fd2cfa711f2a4edec2155ed/href">https://medium.com/media/20ee8caf0fd2cfa711f2a4edec2155ed/href</a></iframe><p>Now that we’ve implemented our own logic for == and hashcode , we decide which instances of Cars are equal. We do this by checking the three properties; color, make and year. We also calculate a new hashcode based on these properties.</p><p>As you can see in the example, car1 and car2 are instantiated with the same properties, so they are now considered <em>equal</em>.</p><p>car3 is completely different, and is therefor <em>not</em> <em>equal</em> to car1.</p><p>How we implement the == and hashcode logic is completely up to us. We could leave out certain properties if we’d like. The important thing is that both == and hashcode behave similar. If == says the objects are equal, then hashcode of both objects should also return the same value.</p><p>As you might think after seeing the example above; “<em>damn, that’s a lot of work for something so simple</em>”. And you are right. In the example above it takes up most of the class for a simple class that only holds a little bit of data.</p><p>Not only that, it is very error prone. The tiniest mistake can lead to unexpected behavior.</p><p>Luckily, there is a solution…</p><h3>Equatable</h3><p>To help you gain the advantages of <em>object equality</em> and prevent you from making application breaking mistakes, there is the <a href="https://pub.dev/packages/equatable">equatable</a> package.</p><p>Equatable will do the necessary overrides for you. All you have to do is tell it which properties to take into account by implementing the props getter.</p><p>Let’s update our example so it uses Equatable.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8156593a44d9304a4359c9d638dce895/href">https://medium.com/media/8156593a44d9304a4359c9d638dce895/href</a></iframe><p>That’s it. This will now result in the same outcome as our tedious manual override.</p><blockquote>If your class already extends another class, you can use the EquatableMixin instead.</blockquote><h3>Using object equality in your code</h3><p>We can now use our new ‘equatable’ objects to write code that’s easier to read, understand and maintain.</p><h4>Comparing objects</h4><p>Let’s start with a simple example of comparing objects that don’t override == and hashcode or implement Equatable.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e9b805497fc1503b1d7a9a2dcd435307/href">https://medium.com/media/e9b805497fc1503b1d7a9a2dcd435307/href</a></iframe><p>As you can see, we have to manually compare each and every property of which we decided to be of importance for our equality check. And this example only has three properties…</p><p>So let’s now do this for when Car extends Equatable.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ce526109079f11cbc9059179c779c325/href">https://medium.com/media/ce526109079f11cbc9059179c779c325/href</a></iframe><p>Now doesn’t this look way better, and less error prone? I think so too!</p><h4>Sets and Maps</h4><p>Not only <strong>we</strong> can use it to compare objects, Dart uses it as well. Sets and Maps use equality comparison to define uniqueness. Set uses equality checks to prevent two equal objects to be entered and Map uses it for the keys.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2111b2ea1c8387c21808e2b9b95a8d6c/href">https://medium.com/media/2111b2ea1c8387c21808e2b9b95a8d6c/href</a></iframe><blockquote>Pro tip: If you want all the distinct objects in a List (of items that extend Equatable ) you can simply do: myList.toSet().</blockquote><h4>Unit tests</h4><p>Last, but certainly not least, Equatable objects make it so much easier for us to write unit tests. Similar to what we’ve done in ‘Comparing objects’ above, we can simplify our expect.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5ca2741dd3284b4de0df403d796545d9/href">https://medium.com/media/5ca2741dd3284b4de0df403d796545d9/href</a></iframe><h3>Conclusion</h3><p>Equatable makes it much easier for us to create comparable objects by allowing us to tell which fields to take into account.</p><p>Comparable objects can improve the readability and maintainability of our code by making comparing shorter and less error prone.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d208e5eb9571" width="1" height="1" alt=""><hr><p><a href="https://medium.com/pinch-nl/comparing-objects-in-dart-made-easy-with-equatable-d208e5eb9571">Comparing objects in Dart made easy with Equatable.</a> was originally published in <a href="https://medium.com/pinch-nl">Pinch.nl</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>