There are many ways to apply dynamic themes in Flutter. Because the Provider is being used by quite a lot of people, let’s try to create applications and set up dynamic themes using Provider.
Setting
Create default Flutter counter application: flutter create counter
I have separated MyHomePage
and _MyHomePageState
into a separate file, lib/home.dart
to make it clearer and to avoid the difficulty of tracking.
We then add the provider library to pubspec.yaml
1 2 3 4 5 6 7 8 9 10 | <span class="token key atrule">dependencies</span> <span class="token punctuation">:</span> <span class="token key atrule">flutter</span> <span class="token punctuation">:</span> <span class="token key atrule">sdk</span> <span class="token punctuation">:</span> flutter <span class="token key atrule">cupertino_icons</span> <span class="token punctuation">:</span> ^0.1.3 <span class="token key atrule">provider</span> <span class="token punctuation">:</span> ^4.0.5 <span class="token comment"># Thêm thư viện provider</span> <span class="token key atrule">dev_dependencies</span> <span class="token punctuation">:</span> <span class="token key atrule">flutter_test</span> <span class="token punctuation">:</span> <span class="token key atrule">sdk</span> <span class="token punctuation">:</span> flutter |
Initialization
Create a file named themes.dart
, which will contain themes and classes related to the theme. First, we will create an enum containing the names of the themes in the class instead of using string to avoid minor errors due to misspellings or error messages if we change the theme name but forgot to change it somewhere in the app. Here I want the app to have 2 themes, light and dark
1 2 | <span class="token keyword">enum</span> AppThemeKeys <span class="token punctuation">{</span> light <span class="token punctuation">,</span> dark <span class="token punctuation">}</span> |
Once done, we will create a Map
containing the properties of the themes included in the app. I want the AppBar, Button in the light theme will be blue, while the dark theme will be green. The brightness attribute will help me automatically change some colors from light to dark to match the theme (for example, in a light theme, the font color is black gray, the background color is white, when the dark theme is used, the font color will be is white, the background is black gray). You can read more about brightness here
1 2 3 4 5 6 7 8 9 10 11 12 13 | <span class="token keyword">final</span> Map <span class="token operator"><</span> AppThemeKeys <span class="token punctuation">,</span> ThemeData <span class="token operator">></span> _themes <span class="token operator">=</span> <span class="token punctuation">{</span> AppThemeKeys <span class="token punctuation">.</span> light <span class="token punctuation">:</span> <span class="token function">ThemeData</span> <span class="token punctuation">(</span> primaryColor <span class="token punctuation">:</span> Colors <span class="token punctuation">.</span> blue <span class="token punctuation">,</span> accentColor <span class="token punctuation">:</span> Colors <span class="token punctuation">.</span> blue <span class="token punctuation">,</span> brightness <span class="token punctuation">:</span> Brightness <span class="token punctuation">.</span> light <span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">,</span> AppThemeKeys <span class="token punctuation">.</span> dark <span class="token punctuation">:</span> <span class="token function">ThemeData</span> <span class="token punctuation">(</span> primaryColor <span class="token punctuation">:</span> Colors <span class="token punctuation">.</span> green <span class="token punctuation">,</span> accentColor <span class="token punctuation">:</span> Colors <span class="token punctuation">.</span> green <span class="token punctuation">,</span> brightness <span class="token punctuation">:</span> Brightness <span class="token punctuation">.</span> dark <span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">,</span> <span class="token punctuation">}</span> <span class="token punctuation">;</span> |
Next we will create the AppTheme
class that inherits ChangeNotifier
, this class will be the class that controls the theme state of our app. You can read more about ChangeNotifier to gain a better understanding of how it works
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | <span class="token keyword">class</span> <span class="token class-name">AppTheme</span> <span class="token keyword">extends</span> <span class="token class-name">ChangeNotifier</span> <span class="token punctuation">{</span> <span class="token comment">// Method này sẽ giúp chúng ta lấy AppTheme ra một cách dễ dàng hơn, thay vì</span> <span class="token comment">// Provider.of<AppTheme>(context)</span> <span class="token comment">// chúng ta chỉ cần</span> <span class="token comment">// AppTheme.of(context)</span> <span class="token keyword">static</span> AppTheme <span class="token function">of</span> <span class="token punctuation">(</span> BuildContext context <span class="token punctuation">,</span> <span class="token punctuation">{</span> bool listen <span class="token operator">=</span> <span class="token boolean">false</span> <span class="token punctuation">}</span> <span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token operator">></span> Provider <span class="token punctuation">.</span> of <span class="token operator"><</span> AppTheme <span class="token operator">></span> <span class="token punctuation">(</span> context <span class="token punctuation">,</span> listen <span class="token punctuation">:</span> listen <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token comment">// Đây sẽ là state chính của class, chứa tên của theme, mặc định mình set là light</span> AppThemeKeys _themeKey <span class="token operator">=</span> AppThemeKeys <span class="token punctuation">.</span> light <span class="token punctuation">;</span> <span class="token comment">// Mình tạo thêm 2 cái getter này để dễ sử dụng hơn</span> <span class="token comment">// currentTheme sẽ là theme hiện tại (là ThemeData không phải tên theme nữa)</span> <span class="token comment">// currentThemeKey là tên theme hiện tại (mình không public _themeKey bởi vì </span> <span class="token comment">// mình không muốn chỉnh sửa trực tiếp vào biến mà qua các setter vì còn cần notifyListeners() nữa)</span> ThemeData <span class="token keyword">get</span> currentTheme <span class="token operator">=</span> <span class="token operator">></span> _themes <span class="token punctuation">[</span> _themeKey <span class="token punctuation">]</span> <span class="token punctuation">;</span> AppThemeKeys <span class="token keyword">get</span> currentThemeKey <span class="token operator">=</span> <span class="token operator">></span> _themeKey <span class="token punctuation">;</span> <span class="token comment">// Đổi theme sang một theme khác </span> <span class="token keyword">void</span> <span class="token function">setTheme</span> <span class="token punctuation">(</span> AppThemeKeys themeKey <span class="token punctuation">)</span> <span class="token punctuation">{</span> _themeKey <span class="token operator">=</span> themeKey <span class="token punctuation">;</span> <span class="token function">notifyListeners</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// Ở đây mình chỉ có 2 theme nên mình sẽ tạo thêm 1 method để demo dễ hơn. Method này sẽ switch giữa</span> <span class="token comment">// light theme và dark theme</span> <span class="token keyword">void</span> <span class="token function">switchTheme</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> _themeKey <span class="token operator">==</span> AppThemeKeys <span class="token punctuation">.</span> dark <span class="token punctuation">)</span> <span class="token punctuation">{</span> _themeKey <span class="token operator">=</span> AppThemeKeys <span class="token punctuation">.</span> light <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span> _themeKey <span class="token operator">=</span> AppThemeKeys <span class="token punctuation">.</span> dark <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">notifyListeners</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
So that’s it for the main part. Following will be used on Flutter’s default app counter
Apply
Open the main.dart
file, at main()
we will need to wrap the whole App in ChangeNotifierProvider
and pass an instance of the AppTheme
we just created into the create
attribute.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <span class="token comment">// Trước</span> <span class="token keyword">void</span> <span class="token function">main</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">runApp</span> <span class="token punctuation">(</span> <span class="token function">MyApp</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token comment">// Sau</span> <span class="token keyword">void</span> <span class="token function">main</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token function">runApp</span> <span class="token punctuation">(</span> <span class="token function">ChangeNotifierProvider</span> <span class="token punctuation">(</span> create <span class="token punctuation">:</span> <span class="token punctuation">(</span> _ <span class="token punctuation">)</span> <span class="token operator">=</span> <span class="token operator">></span> <span class="token function">AppTheme</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">,</span> child <span class="token punctuation">:</span> <span class="token function">MyApp</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> |
Now the widget will be able to get the other instance using Provider.of<AppTheme>(context)
or AppTheme.of(context)
So we will use it always in MyApp
class because it uses MaterialApp
, where we can change the theme. We will change the theme
property from the current value via AppTheme.of(context, listen: true).currentTheme
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | <span class="token comment">// Trước</span> <span class="token keyword">return</span> <span class="token function">MaterialApp</span> <span class="token punctuation">(</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> theme <span class="token punctuation">:</span> <span class="token function">ThemeData</span> <span class="token punctuation">(</span> primarySwatch <span class="token punctuation">:</span> Colors <span class="token punctuation">.</span> blue <span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">,</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token comment">// Sau</span> <span class="token keyword">return</span> <span class="token function">MaterialApp</span> <span class="token punctuation">(</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> theme <span class="token punctuation">:</span> AppTheme <span class="token punctuation">.</span> <span class="token function">of</span> <span class="token punctuation">(</span> context <span class="token punctuation">,</span> listen <span class="token punctuation">:</span> <span class="token boolean">true</span> <span class="token punctuation">)</span> <span class="token punctuation">.</span> currentTheme <span class="token punctuation">,</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> |
We add the listen: true property because we want to update the view whenever the state of AppTheme
changes. So when the state of AppTheme
changed, our theme changed as well. We need to change the theme when clicking on a button in the view, so first we need to get the AppTheme
instance similar to the one above.
At _MyHomePageState
create a variable to contain AppTheme and initialize it in didChangeDependencies
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <span class="token keyword">class</span> <span class="token class-name">_MyHomePageState</span> <span class="token keyword">extends</span> <span class="token class-name">State</span> <span class="token operator"><</span> MyHomePage <span class="token operator">></span> <span class="token punctuation">{</span> AppTheme _theme <span class="token punctuation">;</span> <span class="token metadata symbol">@override</span> <span class="token keyword">void</span> <span class="token function">didChangeDependencies</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span> _theme <span class="token operator">==</span> <span class="token keyword">null</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> _theme <span class="token operator">=</span> AppTheme <span class="token punctuation">.</span> <span class="token function">of</span> <span class="token punctuation">(</span> context <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">super</span> <span class="token punctuation">.</span> <span class="token function">didChangeDependencies</span> <span class="token punctuation">(</span> <span class="token punctuation">)</span> <span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">}</span> |
Then I scroll down to the AppBar declaration and add a button to the right of the AppBar to change the theme. At the onPressed function of the button, I call the switchTheme()
function of AppTheme
to switch between the two themes. I use ?.
just in case _theme
null
1 2 3 4 5 6 7 8 9 10 11 12 | <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> appBar <span class="token punctuation">:</span> <span class="token function">AppBar</span> <span class="token punctuation">(</span> title <span class="token punctuation">:</span> <span class="token function">Text</span> <span class="token punctuation">(</span> widget <span class="token punctuation">.</span> title <span class="token punctuation">)</span> <span class="token punctuation">,</span> actions <span class="token punctuation">:</span> <span class="token punctuation">[</span> <span class="token function">IconButton</span> <span class="token punctuation">(</span> icon <span class="token punctuation">:</span> <span class="token function">Icon</span> <span class="token punctuation">(</span> Icons <span class="token punctuation">.</span> flip <span class="token punctuation">,</span> color <span class="token punctuation">:</span> Colors <span class="token punctuation">.</span> white <span class="token punctuation">)</span> <span class="token punctuation">,</span> onPressed <span class="token punctuation">:</span> _theme <span class="token operator">?</span> <span class="token punctuation">.</span> switchTheme <span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">,</span> <span class="token punctuation">]</span> <span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">,</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> <span class="token punctuation">.</span> |
It is done. And here is the result: