Flutter. Settings screen in seconds with settings_ui package

Yuriy NovikovYuriy Novikov
4 min read

Introduction

I was reading comments on some FlutterDev thread and somebody posted the link to this package.

I checked it out and it is definitely worth adding to my project template.

The package allows for creating Settings pages similar to Android/iPhone native settings.

It is pretty popular, has about 1K likes on pub.dev, and is actively downloaded. The number of downloads (245K/month) says that the package is adopted by enterprises. (Most downloads are made by automated build systems.)

Usage

The package is very straightforward. It has three classes: SettingsList, SettingsSection, and SettingsTile.

We have all of them on the above screen.



class ExampleView extends GetView<ExampleController> {
  const ExampleView({super.key});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Settings example')),
      body: SettingsList(
        sections: [
          SettingsSection(
            title: Text('Settings section'),
            tiles: [
              SettingsTile.navigation(
                leading: Icon(Icons.settings),
                title: Text('Navigation settings tile'),
                onPressed: (context) {
                  Get.toNamed(Routes.ANDROID_SETTINGS);
                },
              ),
              SettingsTile.switchTile(
                initialValue: false,
                onToggle: (_) {},
                title: Text('Switch settings tile'),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

First, we created SettingsList, which took the whole body of the Scaffold. Inside SettingsList we have SettingsSections and inside sections we put SettingsTiles.

There are two implementations of SettingsTile: navigation and switchTile. For some reason, they implemented as different constructors of the same class. Obvious violation of the Single Responsibility Principle.

The settings page can be easily styled for Android using the devicePlatform property of SettingsList:

 return Scaffold(
      appBar: AppBar(title: Text('Settings example')),
      body: SettingsList(
        platform: DevicePlatform.android,  //<-

I have an opinionated practical approach to design which can be explained:

Design for iOS first, then use the same design on both platforms.

So, for me, this Android styling option is kinda useless, but the option is an option — always lovely to have.

What I would like to see instead is more SettingsTile implementations.

Let’s add a missing tile

For almost every settings page we need single and multiple select from the list of choices.

Single select

Since iOS doesn’t have checkboxes, there is an ongoing debate among iOS developers about what to use for multiple select: checkmarks or custom checkboxes.

Though in Flutter we don’t have any problem with checkboxes I vote for checkmarks.

So, let's implement the missing tiles.

Here is the result with CheckmarkListTile widget:

The CheckmarkListTile semantically means the tile with a list of checkmarks.

It is a stateless widget so we need to manage its state in a ViewModel (Controller):

import 'package:get/get.dart';
import 'package:getx_miscellanous/app/global_widgets/checkmark_list_tile.dart';

class CheckmarkExampleController extends GetxController {
  var _singleSelectOptions = [
    CheckmarkListTileOption(title: 'Option 1', checked: true),
    CheckmarkListTileOption(title: 'Option 2', checked: false),
    CheckmarkListTileOption(title: 'Option 3', checked: false),
  ];

  get singleSelectOptions => _singleSelectOptions;

  var _multiSelectOptions = [
    CheckmarkListTileOption(title: 'Option 1', checked: true),
    CheckmarkListTileOption(title: 'Option 2', checked: true),
    CheckmarkListTileOption(title: 'Option 3', checked: false),
    CheckmarkListTileOption(title: 'Option 4', checked: false),
  ];

  get multiSelectOptions => _multiSelectOptions;

  void selectFromSingle(index) {
    if (_singleSelectOptions[index].checked) {
      return;
    }
    for (int i = 0; i < _singleSelectOptions.length; i++) {
      _singleSelectOptions[i].checked = false;
    }
    _singleSelectOptions[index].checked = true;
    update();
  }

  void selectFromMulti(index) {
    int checkedCount = 0;
    for (int i = 0; i < _multiSelectOptions.length; i++) {
      if (_multiSelectOptions[i].checked) {
        checkedCount++;
      }
    }
    if (_multiSelectOptions[index].checked) {
      if (checkedCount > 1) {
        _multiSelectOptions[index].checked = false;
      }
      update();
      return;
    }
    _multiSelectOptions[index].checked = true;
    update();
  }
}

Here is our View:

import 'package:flutter/material.dart';

import 'package:get/get.dart';
import 'package:getx_miscellanous/app/global_widgets/checkmark_list_tile.dart';
import 'package:getx_miscellanous/app/modules/checkmark_example/checkmark_example_controller.dart';
import 'package:settings_ui/settings_ui.dart';


class CheckmarkExampleView extends GetView<CheckmarkExampleController> {
  const CheckmarkExampleView({super.key});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Settings example')),
      body: Padding(
        padding: const EdgeInsets.all(18.0),
        child: GetBuilder<CheckmarkExampleController>(
          builder: (controller) {
            return SettingsList(
              platform: DevicePlatform.android,
              sections: [
                SettingsSection(
                  title: Text('Single select section'),
                  tiles: [
                    CheckmarkListTile(
                       options:  controller.singleSelectOptions, 
                       onTap: controller.selectFromSingle),
                  ],
                ),           
                SettingsSection(
                  title: Text('Multi select section'),
                  tiles: [
                    CheckmarkListTile(
                       options:  controller.multiSelectOptions, 
                       onTap: controller.selectFromMulti),
                  ],
                ),
              ],
            );
          }
        ),
      ),
    );
  }
}

And here is the implementation:

import 'package:flutter/material.dart';
import 'package:settings_ui/settings_ui.dart';
import 'package:animated_checkmark/animated_checkmark.dart';

final class CheckmarkListTile extends AbstractSettingsTile {
  final List<CheckmarkListTileOption> options;
  final Function(int index) onTap;
  final Color? checkmarkColor;
  final Color? backgroundColor;

  const CheckmarkListTile({
    super.key,
    required this.options,
    required this.onTap,
    this.checkmarkColor,
    this.backgroundColor,
  });

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(

          color: backgroundColor ?? Theme.of(context).cardColor,
          border: Border.all(
            color: Colors.white,
            width: 0.5,
          ),
          borderRadius: BorderRadius.all(Radius.circular(10))),
      clipBehavior: Clip.hardEdge,
      child: ListView.builder(
        shrinkWrap: true,
        physics: const NeverScrollableScrollPhysics(),
        itemCount: options.length,
        itemBuilder: (context, index) {
          return Material(
            color: backgroundColor ?? Theme.of(context).cardColor,
            child: InkWell(
              onTap: () {
                onTap(index);
              },
              child: Padding(
                padding:
                    const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
                child: Row(
                  children: [
                    Expanded(
                      child: Text(
                        options[index].title,
                        style: Theme.of(context).textTheme.bodyLarge,
                      ),
                    ),
                    const SizedBox(width: 16),
                    SizedBox(
                      width: 30,
                      height: 30,
                      child: Checkmark(
                        checked: options[index].checked,
                        color: checkmarkColor ?? Theme.of(context).primaryColor,
                        size: 30,
                        weight: 3,
                      ),
                    ),
                  ],
                ),
              ),
            ),
          );
        },
      ),
    );
  }
}

final class CheckmarkListTileOption {
  final String title;
  bool checked;

  CheckmarkListTileOption({required this.title, required this.checked});
}

Note the animated_checkmark import. We need to add it to the project:

flutter pub add animated_checkmark

The CheckmarkListTile can be used without the settings_ui package — just remove the extends AbstractSettingsTile.

Summary

  1. With the settings_ui package, we make the settings pages in seconds.

  2. Since it lacks important widgets we implemented the CheckmarkListTile.

Related:
https://medium.com/easy-flutter/flutter-use-checkmarkbutton-for-multiple-and-single-choices-5402168bac33

Thank you for reading!

0
Subscribe to my newsletter

Read articles from Yuriy Novikov directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Yuriy Novikov
Yuriy Novikov