<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: emmanuelCode</title>
    <description>The latest articles on Forem by emmanuelCode (@emmanuel_code).</description>
    <link>https://forem.com/emmanuel_code</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1030606%2F8a8380c2-941a-4f3c-bf04-3d170c09d7be.png</url>
      <title>Forem: emmanuelCode</title>
      <link>https://forem.com/emmanuel_code</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/emmanuel_code"/>
    <language>en</language>
    <item>
      <title>Implementing the MVI Design Pattern in Flutter: A Journey toward Scalable State Management</title>
      <dc:creator>emmanuelCode</dc:creator>
      <pubDate>Sun, 25 Jan 2026 17:54:38 +0000</pubDate>
      <link>https://forem.com/emmanuel_code/implementing-the-mvi-design-pattern-in-flutter-a-journey-toward-scalable-state-management-250e</link>
      <guid>https://forem.com/emmanuel_code/implementing-the-mvi-design-pattern-in-flutter-a-journey-toward-scalable-state-management-250e</guid>
      <description>&lt;p&gt;A while back, I worked on a small Flutter project while I was learning about Providers. I tried mimicking a simple &lt;strong&gt;MVI design pattern&lt;/strong&gt; from this &lt;a href="https://github.com/kanawish/upvote" rel="noopener noreferrer"&gt;repo&lt;/a&gt; and attempted to create a Flutter version of it. At the time, it received a quick review that was fairly critical because I hadn't fully understood or applied the core concepts yet. Today, I am salvaging that old project in my quest to implement a proper design pattern for scalable projects.&lt;/p&gt;

&lt;p&gt;The original Android app tracks how many times you press the thumbs-up and heart buttons and displays those numbers on the screen. When I revisited my old Flutter version, I couldn't even get it to run. I had to migrate it twice because the &lt;a href="https://docs.flutter.dev/release/breaking-changes/android-v1-embedding-create-deprecation" rel="noopener noreferrer"&gt;Android v1 embedding&lt;/a&gt; and the &lt;a href="https://docs.flutter.dev/release/breaking-changes/flutter-gradle-plugin-apply" rel="noopener noreferrer"&gt;imperative apply of Flutter's Gradle plugins&lt;/a&gt; have since been deprecated. If you are working with an old Flutter project created prior to version 1.12, you may need to use these two migration setups as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is MVI?
&lt;/h2&gt;

&lt;p&gt;After updating the project, I took a step back to ask: what exactly is MVI? &lt;strong&gt;MVI stands for Model-View-Intent&lt;/strong&gt;, where the &lt;strong&gt;Model&lt;/strong&gt; represents the app state, the &lt;strong&gt;View&lt;/strong&gt; is the UI, and the &lt;strong&gt;Intent&lt;/strong&gt; represents user actions.&lt;/p&gt;

&lt;p&gt;In MVI, the &lt;strong&gt;model must be immutable&lt;/strong&gt;. You typically need several methods, such as &lt;code&gt;copyWith&lt;/code&gt; to update the state, comparison operators, &lt;code&gt;toString&lt;/code&gt;, and &lt;code&gt;hashCode&lt;/code&gt;. Writing these manually is extremely error-prone. In Android development, Kotlin provides &lt;strong&gt;data classes&lt;/strong&gt; that handle this automatically, but what about Dart? Fortunately, we have the &lt;strong&gt;freezed&lt;/strong&gt; package to help us out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining the Model
&lt;/h2&gt;

&lt;p&gt;In this Flutter app, the model needs to track the number of thumbs-ups and hearts. By using &lt;strong&gt;Freezed annotations&lt;/strong&gt;, I can generate all the methods I need. Keep in mind that you will need to install the &lt;a href="https://pub.dev/packages/freezed#install" rel="noopener noreferrer"&gt;latest version freezed&lt;/a&gt; package dependencies and generate the code using &lt;code&gt;build_runner&lt;/code&gt; by running &lt;code&gt;dart run build_runner build&lt;/code&gt; (or watch if you want it to update every time the code changes). As of version 2.7.0, you no longer need to pass the &lt;code&gt;-d&lt;/code&gt; flag to &lt;code&gt;build_runner&lt;/code&gt; as it is now automatic. For the best performance, consider using the &lt;a href="https://pub.dev/packages/build_runner" rel="noopener noreferrer"&gt;latest version of build_runner&lt;/a&gt;, as it is expected to receive more performance boosts in the near future; you can follow the progress &lt;a href="https://github.com/dart-lang/build/issues/3800" rel="noopener noreferrer"&gt;here&lt;/a&gt; for details&lt;/p&gt;

&lt;p&gt;Here is the code I used for the model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:freezed_annotation/freezed_annotation.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;part&lt;/span&gt; &lt;span class="s"&gt;'upvote_model.freezed.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;//uncomment this line if parsing factory json constructor&lt;/span&gt;
&lt;span class="c1"&gt;//part 'upvote_model.g.dart';&lt;/span&gt;

&lt;span class="c1"&gt;//creating an immutable data class /hashcode/copyWith/toString etc..&lt;/span&gt;
&lt;span class="c1"&gt;//similiar to kotlin language data class&lt;/span&gt;
&lt;span class="nd"&gt;@freezed&lt;/span&gt;
&lt;span class="kd"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UpvoteModel&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;_$UpvoteModel&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="kd"&gt;factory&lt;/span&gt; &lt;span class="n"&gt;UpvoteModel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;hearts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="kd"&gt;required&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;thumbsUp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_UpvoteModel&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Handling User Intent
&lt;/h2&gt;

&lt;p&gt;In the Android version, Kotlin uses &lt;strong&gt;sealed classes&lt;/strong&gt; (or sealed unions) to help identify user intents. You can think of these as enhanced enums. With &lt;strong&gt;Dart 3.0&lt;/strong&gt;, we can now use the &lt;a href="https://dart.dev/language/class-modifiers#sealed" rel="noopener noreferrer"&gt;sealed class modifier&lt;/a&gt;. For this app, I only needed to track two specific user properties.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// this is the same as a declaring a sealed class in kotlin&lt;/span&gt;
&lt;span class="c1"&gt;// it similar to enums but have more functionnality&lt;/span&gt;
&lt;span class="c1"&gt;// some example: https://www.baeldung.com/kotlin/sealed-class-vs-enum&lt;/span&gt;
&lt;span class="kd"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MainViewEvent&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ThumbsUpClick&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;MainViewEvent&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoveItClick&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;MainViewEvent&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  State Management with Riverpod
&lt;/h2&gt;

&lt;p&gt;For state management, I took this opportunity to learn more about Riverpod. It is a significant improvement over the original Provider package. Providers can be automatically generated for you when using Riverpod annotations. To get started, make sure you have all the necessary &lt;a href="https://riverpod.dev/docs/introduction/getting_started#installing-the-package" rel="noopener noreferrer"&gt;dependencies&lt;/a&gt; and add the &lt;a href="https://pub.dev/packages/riverpod_generator" rel="noopener noreferrer"&gt;riverpod_generator&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Note on Linting
&lt;/h2&gt;

&lt;p&gt;I want to highlight a challenge I faced while installing the &lt;a href="https://riverpod.dev/docs/introduction/getting_started#enabling-riverpod_lint" rel="noopener noreferrer"&gt;riverpod_lint&lt;/a&gt; package. I had some trouble enabling it properly because the official website provides very little installation information. I initially tried placing it under the &lt;code&gt;analyzer&lt;/code&gt; tag in &lt;code&gt;analysis_options.yaml&lt;/code&gt;, but it didn't work. I asked Gemini and Claude for help, but they suggested outdated methods, such as adding a custom lint package, which wasn't what I wanted.&lt;/p&gt;

&lt;p&gt;After digging for hours, I looked into the &lt;a href="https://pub.dev/packages/analysis_server_plugin" rel="noopener noreferrer"&gt;analysis_server_plugin&lt;/a&gt; that Riverpod uses. Instead of just reading the docs, I prompted an AI to explain how it works and how to install and enable your own plugin. With that information, I was able to properly install &lt;code&gt;riverpod_lint&lt;/code&gt; plugin.&lt;/p&gt;

&lt;p&gt;To install it, add it as a &lt;strong&gt;dev dependency&lt;/strong&gt; in your &lt;code&gt;pubspec.yaml&lt;/code&gt;. Here is a snippet of how that file should look:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;upvote&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;an example using riverpod state management with mvi design pattern&lt;/span&gt; 

&lt;span class="na"&gt;publish_to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;none'&lt;/span&gt; 

&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.0.0+1&lt;/span&gt;

&lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;sdk&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^3.10.7&lt;/span&gt;

&lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;flutter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;sdk&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flutter&lt;/span&gt;


  &lt;span class="na"&gt;cupertino_icons&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^1.0.8&lt;/span&gt;
  &lt;span class="na"&gt;riverpod_annotation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^4.0.0&lt;/span&gt;
  &lt;span class="na"&gt;flutter_riverpod&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^3.1.0&lt;/span&gt;
  &lt;span class="na"&gt;freezed_annotation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^3.1.0&lt;/span&gt;

&lt;span class="na"&gt;dev_dependencies&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;flutter_test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;sdk&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flutter&lt;/span&gt;

  &lt;span class="na"&gt;build_runner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^2.10.5&lt;/span&gt;
  &lt;span class="na"&gt;riverpod_generator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^4.0.0+1&lt;/span&gt;
  &lt;span class="na"&gt;riverpod_lint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^3.1.0&lt;/span&gt; &lt;span class="c1"&gt;# your riverpod lint package&lt;/span&gt;
  &lt;span class="na"&gt;flutter_lints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^6.0.0&lt;/span&gt;
  &lt;span class="na"&gt;freezed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^3.2.3&lt;/span&gt;


&lt;span class="na"&gt;flutter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

  &lt;span class="na"&gt;uses-material-design&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To enable it, your &lt;code&gt;analysis_options.yaml&lt;/code&gt; file should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;
&lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;package:flutter_lints/flutter.yaml&lt;/span&gt;

&lt;span class="c1"&gt;# riverpod package lint rules&lt;/span&gt;
&lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
  &lt;span class="na"&gt;riverpod_lint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;^3.1.0&lt;/span&gt;

&lt;span class="na"&gt;analyzer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;linter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

  &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementing the Intent Factory
&lt;/h2&gt;

&lt;p&gt;To handle view events, I created a class that generates a provider for my widgets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:riverpod_annotation/riverpod_annotation.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:upvote/Model/upvote_model.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s"&gt;'package:upvote/View/main_view_event.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;part&lt;/span&gt; &lt;span class="s"&gt;'upvote_intent.g.dart'&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// the annotation here create a provider for me and declare it&lt;/span&gt;
&lt;span class="c1"&gt;// all I need is to add the method I need for when the state change&lt;/span&gt;
&lt;span class="c1"&gt;// and get the "ref" in our flutter widget with "mainViewIntentFactoryProvider"&lt;/span&gt;
&lt;span class="c1"&gt;// variable &lt;/span&gt;
&lt;span class="c1"&gt;// https://riverpod.dev/docs/concepts2/refs#how-to-obtain-a-ref&lt;/span&gt;
&lt;span class="nd"&gt;@riverpod&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MainViewIntentFactory&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;_$MainViewIntentFactory&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// add my model here to begin with initial values&lt;/span&gt;
  &lt;span class="c1"&gt;// I need to override the build method here&lt;/span&gt;
  &lt;span class="c1"&gt;// the "state" variable here is the model itself &lt;/span&gt;
  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="n"&gt;UpvoteModel&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;UpvoteModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;hearts:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;thumbsUp:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="n"&gt;toIntent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MainViewEvent&lt;/span&gt; &lt;span class="n"&gt;mainViewEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mainViewEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;ThumbsUpClick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;copyWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;thumbsUp:&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;thumbsUp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="n"&gt;LoveItClick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;copyWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;hearts:&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;hearts&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the code above, I could have created two separate methods like &lt;code&gt;addHeart()&lt;/code&gt; and &lt;code&gt;addThumbsUp()&lt;/code&gt; while omitting &lt;code&gt;MainViewEvent&lt;/code&gt; entirely. However, in MVI, using &lt;strong&gt;sealed classes&lt;/strong&gt; is a great way to describe your models and makes it much easier to write tests for your state machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the UI
&lt;/h2&gt;

&lt;p&gt;For the widgets, I created two rows inside a column and extended the &lt;code&gt;MyHomePage&lt;/code&gt; class with &lt;code&gt;ConsumerWidget&lt;/code&gt;. This allows me to call my provider methods and listen for changes to rebuild the widgets with their new state. I could have wrapped my column with a &lt;code&gt;Consumer()&lt;/code&gt; widget instead, but I find extending &lt;code&gt;ConsumerWidget&lt;/code&gt; and getting the &lt;code&gt;ref&lt;/code&gt; from the &lt;code&gt;build()&lt;/code&gt; method to be much simpler and less verbose.&lt;br&gt;
there are a few example of using the &lt;a href="https://riverpod.dev/docs/concepts2/consumers" rel="noopener noreferrer"&gt;Consumer&lt;/a&gt; from the Riverpod docs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight dart"&gt;&lt;code&gt;&lt;span class="c1"&gt;// instead of the StatelessWidget we use ConsumerWidget&lt;/span&gt;
&lt;span class="c1"&gt;// to get access to the provider variable&lt;/span&gt;
&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyHomePage&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="n"&gt;ConsumerWidget&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MyHomePage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nd"&gt;@override&lt;/span&gt;
  &lt;span class="c1"&gt;// here we get the WidgetRef ref variable to access our provider&lt;/span&gt;
  &lt;span class="n"&gt;Widget&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BuildContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;WidgetRef&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="c1"&gt;// share the provider variable to it children &lt;/span&gt;
     &lt;span class="c1"&gt;// the watch here is to listen for changes for the upvote model&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="n"&gt;UpvoteModel&lt;/span&gt; &lt;span class="n"&gt;upvoteModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ref&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;watch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mainViewIntentFactoryProvider&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;Scaffold&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;appBar:&lt;/span&gt; &lt;span class="n"&gt;AppBar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nl"&gt;title:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'Upvote Flutter Version'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="nl"&gt;backgroundColor:&lt;/span&gt; &lt;span class="n"&gt;Theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;colorScheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;inversePrimary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="nl"&gt;body:&lt;/span&gt; &lt;span class="n"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="nl"&gt;mainAxisAlignment:&lt;/span&gt; &lt;span class="n"&gt;MainAxisAlignment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Widget&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;[&lt;/span&gt;
                &lt;span class="n"&gt;Row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                  &lt;span class="nl"&gt;mainAxisAlignment:&lt;/span&gt; &lt;span class="n"&gt;MainAxisAlignment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Widget&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;[&lt;/span&gt;
                    &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                      &lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;${upvoteModel.hearts}&lt;/span&gt;&lt;span class="s"&gt; ❤'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="n"&gt;Theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;textTheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;displaySmall&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;SizedBox&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nl"&gt;width:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                      &lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="si"&gt;${upvoteModel.thumbsUp}&lt;/span&gt;&lt;span class="s"&gt; 👍'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                      &lt;span class="nl"&gt;style:&lt;/span&gt; &lt;span class="n"&gt;Theme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;textTheme&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;displaySmall&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="p"&gt;),&lt;/span&gt;
                  &lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;Row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                  &lt;span class="nl"&gt;mainAxisAlignment:&lt;/span&gt; &lt;span class="n"&gt;MainAxisAlignment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="nl"&gt;children:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Widget&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;[&lt;/span&gt;
                    &lt;span class="n"&gt;Padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                      &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;8.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'+❤'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                        &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                          &lt;span class="c1"&gt;// use the notifier inside read to call our intent&lt;/span&gt;
                          &lt;span class="c1"&gt;// method from upvote_intent.dart&lt;/span&gt;
                          &lt;span class="n"&gt;ref&lt;/span&gt;
                              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mainViewIntentFactoryProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;notifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toIntent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LoveItClick&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
                          &lt;span class="n"&gt;debugPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'heart'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="p"&gt;},&lt;/span&gt;
                      &lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="n"&gt;Padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                      &lt;span class="nl"&gt;padding:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;EdgeInsets&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;8.0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                      &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="n"&gt;ElevatedButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                        &lt;span class="nl"&gt;child:&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'+👍'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                        &lt;span class="nl"&gt;onPressed:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                          &lt;span class="n"&gt;ref&lt;/span&gt;
                              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mainViewIntentFactoryProvider&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;notifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                              &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toIntent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ThumbsUpClick&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
                          &lt;span class="n"&gt;debugPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'thumb'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="p"&gt;},&lt;/span&gt;
                      &lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="p"&gt;),&lt;/span&gt;
                  &lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
              &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;And that's it! When I tap on either button, a view event is fired through my intent method. Then, my provider copies the old model state and returns a new one while also listening for any changes to update the view.&lt;/p&gt;

&lt;p&gt;You can view the full Flutter app on GitHub &lt;a href="https://github.com/emmanuelCode/upvote-flutter" rel="noopener noreferrer"&gt;here&lt;/a&gt;. I'm still relatively new to MVI and looking to improve, so if you spot any errors or have suggestions, please let me know!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article has been reviewed and refined with the assistance of NotebookLM to ensure technical accuracy and clarity.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>dart</category>
      <category>riverpod</category>
      <category>mvi</category>
    </item>
    <item>
      <title>How to Fix FlutterFire CLI Errors in the "Get to know Firebase for Flutter" Codelab</title>
      <dc:creator>emmanuelCode</dc:creator>
      <pubDate>Thu, 22 Jan 2026 13:08:29 +0000</pubDate>
      <link>https://forem.com/emmanuel_code/how-to-fix-flutterfire-cli-errors-in-the-get-to-know-firebase-for-flutter-codelab-19i1</link>
      <guid>https://forem.com/emmanuel_code/how-to-fix-flutterfire-cli-errors-in-the-get-to-know-firebase-for-flutter-codelab-19i1</guid>
      <description>&lt;p&gt;I wanted to share a specific hurdle I encountered while working through the "&lt;a href="https://firebase.google.com/codelabs/firebase-get-to-know-flutter" rel="noopener noreferrer"&gt;&lt;strong&gt;Get to know Firebase for Flutter&lt;/strong&gt;&lt;/a&gt;" codelab and explain how to resolve it. For context, I was using &lt;strong&gt;VS Code&lt;/strong&gt; on &lt;strong&gt;Nobara OS&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Everything went smoothly from &lt;strong&gt;Step 1&lt;/strong&gt; through &lt;strong&gt;Step 3&lt;/strong&gt;. However, I ran into a bit of trouble at &lt;strong&gt;Step 4&lt;/strong&gt;, where you're required to add the &lt;strong&gt;FlutterFire&lt;/strong&gt; libraries. If you're a beginner, you might struggle here because, while the documentation mentions the &lt;strong&gt;Firebase CLI&lt;/strong&gt;, it’s easy to overlook the authentication requirements or the specific installation nuances.&lt;/p&gt;

&lt;p&gt;If the CLI isn't set up correctly, running &lt;code&gt;flutterfire configure&lt;/code&gt; will return an error in your terminal like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;··• flutterfire configure

i Found 0 Firebase projects.

Failed to fetch your Firebase projects. Fetch failed with this: FirebaseCommandException: An error occured on the Firebase CLI when attempting to run a command.

COMMAND: firebase projects:list --json

ERROR: Failed to authenticate, have you run firebase login?

✔ Would you like to create a new Firebase project? · yes

✔ Enter a project id for your new Firebase project (e.g. my-cool-project) · fir-flutter-codelab-2d9cc

⠦ Creating new Firebase project fir-flutter-codelab-2d9cc...
FirebaseCommandException: An error occured on the Firebase CLI when attempting to run a command.

COMMAND: firebase projects:create fir-flutter-codelab-2d9cc --json

ERROR: Failed to authenticate, have you run firebase login?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;To resolve this, you first need to ensure &lt;strong&gt;Node.js&lt;/strong&gt; is installed on your system. If you haven't set it up yet, you can find the installers at the official &lt;a href="https://nodejs.org/en/download" rel="noopener noreferrer"&gt;Node.js website&lt;/a&gt;.&lt;br&gt;
Once &lt;strong&gt;Node.js&lt;/strong&gt; is ready, run the following command to install the &lt;strong&gt;Firebase CLI&lt;/strong&gt; globally: &lt;code&gt;npm install -g firebase-tools&lt;/code&gt;&lt;br&gt;
While there are other ways to install the &lt;strong&gt;Firebase CLI&lt;/strong&gt;—such as using &lt;a href="https://firebase.google.com/docs/cli#mac-linux-standalone-binary" rel="noopener noreferrer"&gt;standalone binaries&lt;/a&gt; or &lt;a href="https://firebase.google.com/docs/cli#mac-linux-auto-script" rel="noopener noreferrer"&gt;auto-install scripts&lt;/a&gt;—I've found it much easier to maintain via &lt;strong&gt;npm&lt;/strong&gt;. Using &lt;strong&gt;npm&lt;/strong&gt; automatically sets up the &lt;code&gt;firebase&lt;/code&gt; global command in your &lt;strong&gt;PATH&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In contrast, some binary scripts require you to manually configure your &lt;strong&gt;PATH&lt;/strong&gt; or rename the file so that the terminal recognizes the &lt;code&gt;firebase&lt;/code&gt; command. If the &lt;strong&gt;FlutterFire CLI&lt;/strong&gt; can't detect that command, it will throw an error. Using &lt;strong&gt;npm&lt;/strong&gt; simply makes it easier to manage and update in the long run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Steps
&lt;/h2&gt;

&lt;p&gt;After the installation is complete, make sure to run the login command in your terminal: &lt;code&gt;firebase login&lt;/code&gt;&lt;br&gt;
Once you've authenticated your Google account, you can proceed with &lt;code&gt;flutterfire configure&lt;/code&gt; in your project directory. After getting past this CLI hurdle, the rest of the codelab went perfectly.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article has been reviewed and refined with the assistance of NotebookLM to ensure technical accuracy and clarity.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>flutter</category>
      <category>firebase</category>
      <category>flutterfire</category>
      <category>codelab</category>
    </item>
  </channel>
</rss>
