Dynamically changing the app text theme in a Flutter app

Issue

I am using a standard flutter text theme and utilise the Theme.of(context).textTheme... way of setting styling on specific items throughout my app.

I would like to provide a user preference setting to adjust the text size between "small", "normal" and "large". For example scale the text size by x0.75, x1.0 and x1.25 respectively.

I am able to implement a Slider on a user settings screen which returns one of those values.

Now the challenge is to notify all the widgets that the text must be rendered with a new larger Text Size.

What I did in a previous app is to use a custom ChangeNotifier for the text theme. This class has a method to set the scale on all the "styles" in it’s inventory and then notify listeners to rebuild. While that works fine it is incredibly cumbursome. Maintaining and even using this custom "Theme Provider" works but is a manual and labor intensive torture.

I am hoping for a simple, neat way to allow some variation of the following:

MyTextSizePicker(
  onChanged: (double newScalingFactor) {
    Theme.of(context, listen: false).scaleTextTheme(newScalingFactor);
  });

Where ideally I don’t have to re-implement scaleTextTheme manually, but even if I have to do that, at most it should allow each of the "standard" TextStyles to be updated with a new fontSize, eg some kind of

bodyText1 = bodyText1.copyWith(fontSize: baseBodyText1.fontSize * scalingFactor);

I find it surprising that there is no methods on the standard Theme in flutter to update the theme of a running app.

Solution

The best option to avoid the boilerplate of changing the size of the text in each widget is to use provider to change the Theme of your app

return ChangeNotifierProvider<ThemeProvider>(
  create: (_) => ThemeProvider(),
  builder: (context, _){
    final ThemeProvider themeData = context.watch<ThemeProvider>();
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: themeData.theme,
      onGenerateRoute: Routes.getRoute,
      initialRoute: homeRoute,
    );
  },
),

class ThemeProvider with ChangeNotifier{
  double _scale = 1;

  //Or create your own theme and keep its reference somewhere
  ThemeData get theme => ThemeData.light()
    ..textTheme.apply(fontSizeFactor: _scale)
    ..accentTextTheme.apply(fontSizeFactor: _scale)
    ..primaryTextTheme.apply(fontSizeFactor: _scale);

  themeScale(double newScale) {
    if(newScale == _scale) return;
    _scale = newScale
    notifyListeners();
  }
}

Then just call your textSizePicker like this

MyTextSizePicker(
  onChanged: (double newScalingFactor) {
    Provider.of<ThemeProvider>(context, listen: false).themeScale(newScalingFactor);
});

That will rebuild all widgets that have text without you to manually change their style : Theme.of(context).bodyText1.copyWith(fontSize: baseBodyText1.fontSize * scalingFactor);

Answered By – EdwynZN

Answer Checked By – David Marino (FlutterFixes Volunteer)

Leave a Reply

Your email address will not be published.