🔧

【Flutter】settings_uiを試す

2022/12/03に公開

概要

settings_ui パッケージを使用してアプリの設定画面を実装する方法を色々試してみたいと思います。

動作環境

- macOS Monterey バージョン12.6 Apple M1
- Flutter SDK バージョン 3.3.8
- iOS シュミレータ iPhone 14 Pro iOS 16.0
- Android シュミレータ Pixel 4 API 32

パッケージインストール

pubspec.yaml
dependencies:
  settings_ui: ^2.0.2

最小限の構成で実装してみる

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

class SettingsPage extends HookConsumerWidget {
  const SettingsPage({Key? key}) : super(key: key);

  
  Widget build(BuildContext context, WidgetRef ref) {
    return SettingsList(
      sections: [
        SettingsSection(
          title: const Text('セクション'),
          tiles: <SettingsTile>[
            SettingsTile.navigation(
              leading: const Icon(Icons.language),
              title: const Text('Language'),
              value: const Text('日本語'),
            ),
          ],
        ),
      ],
    );
  }
}

↑の実装をAndroid/iOSそれぞれで実行すると結果は以下になります。

Android iOS
image1 image2

基本的な構成としては

  • SettingsList
    • sections に複数の AbstractSettingsSection を持つ
  • SettingsSection
    • tiles に複数の AbstractSettingsTile を持つ
    • カスタム実装用に CustomSettingsSection が存在する
  • SettingsTile
    • カスタム実装用に CustomSettingsTile が存在する

↑の3つを使用して設定画面を構築していきます。

各Widget

SettingsList

sections パラメータに AbstractSettingsSection 一覧を設定します。また他指定できるパラメータで気になったものを以下に特筆してます。

DevicePlatform platform パラメータ

デフォルトだとOS毎の見た目にしてくれるみたいですが、このパラメータを設定しておくと好きなOSの見た目に固定できます。

先ほど↑の実装を platform = iOS に固定してみたいと思います。

class SettingsPage extends HookConsumerWidget {
  const SettingsPage({Key? key}) : super(key: key);

  
  Widget build(BuildContext context, WidgetRef ref) {
    return SettingsList(
      platform: DevicePlatform.iOS, // ← 追加
      sections: [
            ...
          ],
        ),
      ],
    );
  }
}

↓ AndroidもiOSと同じ見た目になりました。
(どちらも platform: DevicePlatform.iOS で実行してます)

Android iOS
image3 image4

全platformの値で試してみた結果以下の様になりました。※ 試した端末はiOSシュミレータです。

platform スクリーンショット
DevicePlatform.android image5
DevicePlatform.fuchsia image6
DevicePlatform.iOS image7
DevicePlatform.linux image8
DevicePlatform.macOS image9
DevicePlatform.windows image10
DevicePlatform.web image11

ApplicationType applicationType パラメータ

materialcupertinoboth を指定できます。
ドキュメントだけだとあんまり詳細が分かりませんでしたが、実装を見る限り brightness の参照元を決めるのに使われているみたいです。

実装部分

  Brightness calculateBrightness(BuildContext context) {
    final materialBrightness = Theme.of(context).brightness;
    final cupertinoBrightness = CupertinoTheme.of(context).brightness ??
        MediaQuery.of(context).platformBrightness;

    switch (applicationType) {
      case ApplicationType.material:
        return materialBrightness;
      case ApplicationType.cupertino:
        return cupertinoBrightness;
      case ApplicationType.both:
        return platform != DevicePlatform.iOS
            ? materialBrightness
            : cupertinoBrightness;
    }
  }

SettingsSection

tiles パラメータに AbstractSettingsTile 一覧を設定します。
他指定できるパラメータとして

  • title (Widget)
  • margin (EdgeInsetsDirectional)

を指定できます。

SettingsTile

実際の設定項目を定義します。必須パラメータは title のみになっています。
また SettingsTile には以下の Named constructors が用意されています。

  • SettingsTile.navigation
  • SettingsTile.switchTile

↑に関しては後述しています。まずは通常のコンストラクタで作成した場合を見ていきます。

titleのみ設定

まずは単純に title のみ設定した場合を試してみます。

    return SettingsList(
      platform: DevicePlatform.iOS,
      sections: [
        SettingsSection(
          title: const Text('セクション'),
          tiles: <SettingsTile>[SettingsTile(title: const Text('タイトル'))],
        ),
      ],
    );

↑の実行結果は以下の様になります。
image12

leadingtrailing を設定

次に、ListTile と同じ様に Widget leadingWidget trailing を設定できるのでそちらも試してみます。

    return SettingsList(
      platform: DevicePlatform.iOS,
      sections: [
        SettingsSection(
          title: const Text('セクション'),
          tiles: <SettingsTile>[
            SettingsTile(
              leading: const FlutterLogo(), // ← 追加
              title: const Text('タイトル'),
              trailing: const Icon(Icons.more_vert), // ← 追加
            ),
          ],
        ),
      ],
    );

↑の実行結果としては以下の様になります。
image13

descriptionvalue

補足文を表示させる description や、設定値を表示させる value を設定できます。PlarformSettingsTile.navigation で表示させた場合で挙動が違ってきます。

以下の実装で PlarformNamed constructors を使用した際の表示をまとめてみました。
また、trailing を指定すると value が表示されないので削除してます。

SettingsTile(
  leading: const FlutterLogo(),
  title: const Text('タイトル'),
  description: const Text('description'),
  value: const Text('value'),
)
Platform コンストラクタ 表示結果
DevicePlatform.iOS 通常 image14
DevicePlatform.android 通常 image15
DevicePlatform.web 通常 image16
DevicePlatform.iOS SettingsTile.navigation image17
DevicePlatform.android SettingsTile.navigation image18
DevicePlatform.web SettingsTile.navigation image19

※ Platformに関しては iOS ( macOS / windows も同じ), android (fuchsia / linux も同じ), web で比較してます。

SettingsTile.switchTile

ON/OFF の設定項目を作成する際に使用します。
以下の実装で、各 Platform 毎の表示の違いを以下にまとめてみました。

SettingsTile.switchTile(
  leading: const FlutterLogo(),
  title: const Text('タイトル'),
  description: const Text('description'),
  initialValue: true,
  onToggle: (value) {},
)
Platform 表示結果
DevicePlatform.iOS image20
DevicePlatform.android image21
DevicePlatform.web image22

※ Platformに関しては iOS ( macOS / windows も同じ), android (fuchsia / linux も同じ), web で比較してます。

その他: package_info_plusを使用してアプリのバージョン表示

package_info_plusパッケージを使用して、設定画面によくあるアプリのバージョンを表示してみたいと思います。

パッケージインストール

dependencies:
  package_info_plus: ^3.0.2

実装例

class SettingsPage extends HookConsumerWidget {
  const SettingsPage({Key? key}) : super(key: key);

  
  Widget build(BuildContext context, WidgetRef ref) {
    final fromPlatform = useMemoized(PackageInfo.fromPlatform);
    final snapshot = useFuture(fromPlatform);
    if (!snapshot.hasData) {
      return const SizedBox.shrink();
    }

    return SettingsList(
      platform: DevicePlatform.iOS,
      sections: [
        SettingsSection(
          title: const Text('セクション'),
          tiles: <SettingsTile>[
            SettingsTile(
                leading: const Icon(Icons.info),
                title: const Text('アプリのバージョン'),
                value: Text("${snapshot.data?.version}"))
          ],
        ),
      ],
    );
  }
}

実行結果は↓の様に表示されます。
image23

その他: 「お問い合わせ」項目を追加し、タップするとメーラーが立ち上がる様にする

url_launcherを使用して項目をタップするとメーラーが起動するようにしてみます。

パッケージインストール

dependencies:
  url_launcher: ^6.1.7

事前準備

  • Android

    • AndroidManifest.xml に以下を追加しておく

      <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.xxxx">
      
        <application android:label="@string/app_name" android:name="${applicationName}" android:icon="@mipmap/ic_launcher">
          ...
        </application>
        <!-- 以下を追加 -->
        <queries>
          <intent>
            <action android:name="android.intent.action.VIEW" />
            <data android:scheme="mailto" />
          </intent>
        </queries>
      </manifest>
      
  • iOS

    • Info.plist に以下を追加しておく

      <key>LSApplicationQueriesSchemes</key>
      <array>
        <string>mailto</string>
      </array>
      

実装例

class SettingsPage extends HookConsumerWidget {
  const SettingsPage({Key? key}) : super(key: key);

  
  Widget build(BuildContext context, WidgetRef ref) {
    return SettingsList(
      platform: DevicePlatform.iOS,
      sections: [
        SettingsSection(
          title: const Text('サポート'),
          tiles: <SettingsTile>[
            SettingsTile.navigation(
              leading: const Icon(Icons.mail),
              title: const Text('お問い合わせ'),
              onPressed: (context) => _launchMailer(),
            ),
          ],
        ),
      ],
    );
  }

  String? _encodeQueryParameters(Map<String, String> params) {
    return params.entries
        .map((MapEntry<String, String> e) =>
            '${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value)}')
        .join('&');
  }

  Future<void> _launchMailer() async {
    const address = 'メールアドレス';
    final Uri uri = Uri(
      scheme: 'mailto',
      path: address,
      query: _encodeQueryParameters(<String, String>{
        'subject': 'メールの件名',
      }),
    );
    if (await canLaunchUrl(uri)) {
      await launchUrl(uri);
    } else {
      // エラー処理
    }
  }
}

動作イメージとしては以下の様になります。
image24

参考URL

Discussion