FlutterでMobileだけでなくWebアプリも作る際に気をつける事

投稿者: | 2024年9月6日

FlutterでMobileに加えWebアプリケーションを開発する際の注意点

はじめに

FlutterはGoogle I/O 2024で発表されたようにWebAssembly対応が安定軌道に入った。格段にパフォーマンスが向上したとの報告もある。

今後、Flutterを導入する場合、Web対応も考慮するということもあるだろう。その際、知っておかなくてはいけないことを少し記しておく。

1. Localeの設定

Flutterアプリケーションでは、適切なLocale設定が重要である。特にWebアプリケーションにおいては、Localeが正しく設定されていないとランタイムでクラッシュする可能性がある。以下の点に注意する必要がある:

  • MaterialAppまたはCupertinoAppウィジェットでlocalizationsDelegatessupportedLocalesを適切に設定する。
  • 国際化対応は、プロジェクト開始後なるべく早期に導入することが望ましい。

例:

return MaterialApp(
  localizationsDelegates: [
    GlobalMaterialLocalizations.delegate,
    GlobalWidgetsLocalizations.delegate,
    GlobalCupertinoLocalizations.delegate,
  ],
  supportedLocales: [
    const Locale('en', 'US'),
    const Locale('ja', 'JP'),
  ],
  // その他の設定...
);

2. Webアプリケーションの場合分け

Webプラットフォームでは、一部のプラットフォーム固有のAPIが利用できない。特にdart:ioパッケージのPlatformクラスはWebでは動作しない。そのため、以下の対応が必要である:

  • kIsWeb定数を使用してWebプラットフォームを検出する。
  • Webと他のプラットフォームで異なる実装を提供する。

例:

import 'package:flutter/foundation.dart' show kIsWeb;

Widget build(BuildContext context) {
  if (kIsWeb) {
    return WebSpecificWidget();
  } else {
    return MobileSpecificWidget();
  }
}

3. iOS/macOSでのCupertinoデザインの使用

iOSおよびmacOSプラットフォームでは、ネイティブな外観を実現するためにCupertinoデザインを使用することが推奨される。以下の点に注意する:

  • プラットフォームに応じて適切なウィジェットを選択する。
  • Theme.of(context).platformを使用してプラットフォームを判定する。

例:

Widget build(BuildContext context) {
  if (kIsWeb || Theme.of(context).platform == TargetPlatform.android) {
    return MaterialApp(
      // Material Designの設定
    );
  } else {
    return CupertinoApp(
      // Cupertinoデザインの設定
    );
  }
}

実装例

以下に、上記の注意点を考慮したサンプルコードを示す:

まずはpubspec.yamlに国際化対応を入れてpub getしておく

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter

サンプルアプリのソースコード

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
import 'package:flutter_localizations/flutter_localizations.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return _buildApp();
  }

  Widget _buildApp() {
    if (kIsWeb || !Platform.isIOS && !Platform.isMacOS) {
      return _buildMaterialApp();
    } else {
      return _buildCupertinoApp();
    }
  }

  Widget _buildMaterialApp() {
    return MaterialApp(
      title: 'Flutter Cross-Platform App',
      localizationsDelegates: const [
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: const [
        Locale('en', 'US'),
        Locale('ja', 'JP'),
      ],
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const HomePage(),
    );
  }

  Widget _buildCupertinoApp() {
    return const CupertinoApp(
      title: 'Flutter Cross-Platform App',
      localizationsDelegates: [
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
      ],
      supportedLocales: [
        Locale('en', 'US'),
        Locale('ja', 'JP'),
      ],
      theme: CupertinoThemeData(primaryColor: Colors.blue),
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return _buildHomePageBody(context);
  }

  Widget _buildHomePageBody(BuildContext context) {
    if (kIsWeb || Theme.of(context).platform == TargetPlatform.android) {
      return Scaffold(
        appBar: AppBar(title: const Text('Cross-Platform Demo')),
        body: Center(
          child: ElevatedButton(
            onPressed: () => _showDialog(context),
            child: const Text('Open Dialog'),
          ),
        ),
      );
    } else {
      return CupertinoPageScaffold(
        navigationBar: const CupertinoNavigationBar(
          middle: Text('Cross-Platform Demo'),
        ),
        child: Center(
          child: CupertinoButton(
            onPressed: () => _showDialog(context),
            child: const Text('Open Dialog'),
          ),
        ),
      );
    }
  }

  void _showDialog(BuildContext context) {
    if (kIsWeb || Theme.of(context).platform == TargetPlatform.android) {
      showDialog(
        context: context,
        builder: (BuildContext context) {
          return AlertDialog(
            title: const Text('Material Dialog'),
            content: const Text('This is a Material Design dialog.'),
            actions: <Widget>[
              TextButton(
                child: const Text('OK'),
                onPressed: () => Navigator.of(context).pop(),
              ),
            ],
          );
        },
      );
    } else {
      showCupertinoDialog(
        context: context,
        builder: (BuildContext context) {
          return CupertinoAlertDialog(
            title: const Text('Cupertino Dialog'),
            content: const Text('This is an iOS-style dialog.'),
            actions: <Widget>[
              CupertinoDialogAction(
                child: const Text('OK'),
                onPressed: () => Navigator.of(context).pop(),
              ),
            ],
          );
        },
      );
    }
  }
}

このサンプルコードは、Localeの設定、Webの場合分け、iOS/macOSでのCupertinoデザインの使用を考慮している。

結論

FlutterでMobileおよびWebアプリケーションを開発する際は、プラットフォーム間の違いを適切に処理することが重要である。Localeの設定、Webプラットフォームの特殊性、そしてiOS/macOS向けのCupertinoデザインの使用を考慮することで、クロスプラットフォームでの一貫性と各プラットフォーム固有の最適化を両立できる。これらの注意点を踏まえた上で、アプリケーションの要件に応じて適切な実装を行うことが求められる。

コメントを残す