Flutter シリーズの記事 - Flutter Advanced 2

このセクションでは、ナビゲーションとルーティング、状態管理、非同期処理、HTTP リクエストと Rest API、データの永続化など、Flutter の高度なトピックを詳しく紹介します。これらのトピックを 1 つずつ見ていきましょう。

1. ナビゲーションとルーティング

Flutter では、ナビゲーションとルーティングがマルチページ アプリケーションを構築するための重要な概念です。ナビゲーションとは、あるページ (またはルーティング) から別のページに切り替えるプロセスです。各ページはウィジェットに対応します。Flutterでは、ページの切り替えはNavigatorによって管理されます。

1.1. 基本的なナビゲーション

Flutter では、MaterialApp を使用してナビゲーション スタックを管理します。新しい MaterialApp が作成されると、自動的にルーティング スタックが作成され、スタックの最上位に Navigator が配置されます。

新しいページに移動するには、Navigator.push() メソッドを使用できます。

Navigator.push(context, MaterialPageRoute(builder: (context) => SecondPage()));

前のページに戻るには、Navigator.pop() メソッドを使用します。

Navigator.pop(context);

1.2. ルートの命名

Flutter は名前付きルーティングもサポートしているため、アプリ内のナビゲーションに読みやすい名前を使用できます。名前付きルートを使用するには、まず、MaterialApp でルート テーブルを定義します。

MaterialApp(
  routes: {
    '/': (context) => HomePage(),
    '/second': (context) => SecondPage(),
  },
)

これで、ナビゲーションに名前付きルートを使用できるようになります。

Navigator.pushNamed(context, '/second');

1.3. パラメータを使用したルーティング

場合によっては、新しいページにパラメータを渡す必要があります。Flutter では、ModalRoute.of() を使用してルート内のパラメーターを取得できます。

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final args = ModalRoute.of(context).settings.arguments as Map<String, dynamic>;
    // 使用参数
    return Scaffold(...);
  }
}

パラメータを渡すには、ナビゲーション時にパラメータを渡すことができます。

Navigator.pushNamed(context, '/second', arguments: {'name': 'John', 'age': 30});

1.4. ルーティング遷移アニメーション

Flutter は、グラデーション、ズーム、パンなど、豊富なルーティング遷移アニメーション効果を提供します。MaterialPageRoute で PageTransitionsBuilder を設定して、トランジション アニメーションをカスタマイズできます。

MaterialApp(
  routes: {
    '/': (context) => HomePage(),
    '/second': (context) => SecondPage(),
  },
  theme: ThemeData(
    pageTransitionsTheme: PageTransitionsTheme(
      builders: {
        TargetPlatform.android: CupertinoPageTransitionsBuilder(), // 使用iOS样式的转场动画
      },
    ),
  ),
)

これはナビゲーションとルーティングの基本的な紹介にすぎません。Flutter は、ヒーロー アニメーション、ルーティング インターセプトなど、さらに多くのナビゲーションとルーティング機能を提供します。公式ドキュメントとサンプルコードを読むことで、ナビゲーションとルーティングについて詳しく学ぶことができます。

2. 状態管理

Flutter では、状態管理は、アプリケーション内の異なるページ間での共有データと状態の変更を処理する重要な側面です。Flutter にはさまざまな状態管理ソリューションがあり、その中でも Provider、Riverpod、Bloc がより人気があります。

2.1. プロバイダー

Provider は、軽量で使いやすい状態管理ライブラリです。これにより、ウィジェット ツリーでデータを共有したり、Consumer または Provider.of を通じてデータを取得したりすることができます。

まず、アプリケーションのルート Widget に ChangeNotifierProvider を作成し、共有するデータ モデルをその中に置きます。

void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => CounterModel(),
      child: MyApp(),
    ),
  );
}

次に、データを使用する必要があるウィジェットで、Consumer を使用してデータ変更をサブスクライブします。

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final counter = context.watch<CounterModel>();
    return Text('Count: ${counter.count}');
  }
}

CounterModel のデータが変更されると、MyWidget は自動的に更新されます。

2.2. リバーポッド

Riverpod は新しい状態管理ライブラリであり、Provider の改良版です。Riverpod は、より優れたパフォーマンスとクリーンな API を提供します。

Riverpod を使用するには、まずプロバイダーを作成します。

final counterProvider = Provider<int>((ref) => 0);

次に、ProviderListener を使用してデータ変更をサブスクライブします。

class MyWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, ScopedReader watch) {
    final counter = watch(counterProvider);
    return Text('Count: $counter');
  }
}

2.3. ブロック

Bloc は、一方向のデータ フローを使用して状態を管理する、もう 1 つの一般的に使用される状態管理ライブラリです。ブロックは状態を操作から分離するため、コードの保守とテストが容易になります。

まず、ブロックを作成します。

enum CounterEvent { increment, decrement }

class CounterBloc extends Bloc<CounterEvent, int> {
  @override
  int get initialState => 0;

  @override
  Stream<int> mapEventToState(CounterEvent event) async* {
    switch (event) {
      case CounterEvent.increment:
        yield state + 1;
        break;
      case CounterEvent.decrement:
        yield state - 1;
        break;
    }
  }
}

次に、Bloc を使用する必要があるウィジェットで、BlocBuilder を使用して状態の変更をサブスクライブします。

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<CounterBloc, int>(
      builder: (context, state) {
        return Text('Count: $state');
      },
    );
  }
}

これは状態管理の基本的な紹介にすぎません。プロバイダー、リバーポッド、ブロックはすべて、より多くの機能と高度な使用法を提供します。状態管理を深く学ぶには時間と練習が必要ですが、公式ドキュメントやサンプルコードを読むことで、より多くのスキルとベストプラクティスを習得できます。

3. 非同期処理

Flutter では、ネットワークからのデータの取得、ローカル ファイルの読み取りなど、非同期処理が非常に一般的です。Flutter は、非同期操作を処理するために Future と Stream を提供します。

3.1. 未来

Future は、完了または失敗する可能性のある非同期操作を表します。非同期タスクを実行するには、async および await キーワードを使用します。

Future<String> fetchData() async {
  // 模拟网络请求
  await Future.delayed(Duration(seconds: 2));
  return 'Data from server';
}

void main() {
  fetchData().then((data) {
    print(data);
  }).catchError((error) {
    print('Error: $error');
  });
}

3.2. ストリーム

ストリームは、一連の非同期イベントを表します。Future とは異なり、Stream は単一の結果ではなく複数の値を生成できます。

ストリームを作成するには、StreamController を使用できます。

Stream<int> countStream() {
  final controller = StreamController<int>();
  Timer.periodic(Duration(seconds: 1), (timer) {
    controller.add(timer.tick);
  });
  return controller.stream;
}

void main() {
  countStream().listen((count) {
    print('Count: $count');
  });
}

これは非同期処理の基本的な紹介にすぎませんが、Flutter には、非同期操作をより便利に処理できる async* や await for などの非同期ツールや関数も提供されています。非同期処理を深く学ぶには練習と継続的な挑戦が必要ですので、実際のプロジェクトで習得していただければ幸いです。

4. HTTPリクエストとRest API

サーバーとの対話は、最新のアプリケーションでは一般的な要件です。Flutter は、HTTP リクエストを作成し、Rest API を処理するためのさまざまな方法を提供します。

4.1. http パッケージの使用

Flutter の http パッケージは、HTTP リクエストの送信と応答の処理を可能にする使いやすい HTTP リクエスト ライブラリです。

まず、http パッケージの依存関係を pubspec.yaml ファイルに追加します。

dependencies:
  flutter:
    sdk: flutter
  http: ^0.13.3

次に、http パッケージを使用して HTTP リクエストを送信できます。

import 'package:http/http.dart' as http;

Future<void> fetchData() async {
  final url = Uri.parse('https://api.example.com/data');
  final response = await http.get(url);

  if (response.statusCode == 200) {
    print('Response: ${response.body}');
  } else {
    print('Error: ${response.statusCode}');
  }
}

4.2. Dio パッケージの使用

dio は、より豊富な機能と使いやすい API を提供する、もう 1 つの人気のある HTTP リクエスト ライブラリです。

まず、dio パッケージの依存関係を pubspec.yaml ファイルに追加します。

dependencies:
  flutter:
    sdk: flutter
  dio: ^4.0.0

次に、 dio パッケージを使用して HTTP リクエストを送信できます。

import 'package:dio/dio.dart';

Future<void> fetchData() async {
  final dio = Dio();
  final url = 'https://api.example.com/data';
  final response = await dio.get(url);

  if (response.statusCode == 200) {
    print('Response: ${response.data}');
  } else {
    print('Error: ${response.statusCode}');
  }
}

4.3. JSON データの処理

通常、サーバーから返されるデータは JSON 形式です。Flutter では、dart:convert パッケージを使用して JSON データを解析およびシリアル化できます。

import 'dart:convert';

void main() {
  final jsonString = '{"name": "John", "age": 30}';
  final jsonData = jsonDecode(jsonString);

  print('Name: ${jsonData['name']}');
  print('Age: ${jsonData['age']}');
}

これは、HTTP リクエストと JSON データの処理についての基本的な紹介にすぎません。実際のプロジェクトでは、エラーを処理したり、モデル クラスを使用してデータをシリアル化したりする必要がある場合もあります。公式ドキュメントとサンプル コードを学習することで、HTTP リクエストと Rest API についてさらに詳しく学習できることを願っています。

5. データの永続性

アプリケーション内のデータの永続性は不可欠であり、Flutter はデータのローカル ストレージを実現するさまざまな方法を提供します。

5.1.shared_preferencesパッケージの使用

shared_preferences は、キーと値のペアのデータを保存できる使いやすいローカル リポジトリです。

まず、shared_preferences パッケージの依存関係を pubspec.yaml ファイルに追加します。

dependencies:
  flutter:
    sdk: flutter
  shared_preferences: ^2.0.9

次に、shared_preferences パッケージを使用してデータの読み取りと書き込みを行うことができます。

import 'package:shared_preferences/shared_preferences.dart';

void main() async {
  final prefs = await SharedPreferences.getInstance();

  // 保存数据
  await prefs.setString('username', 'John');

  // 读取数据
  final username = prefs.getString('username');
  print('Username: $username');
}

5.2. sqflite パッケージの使用

sqflite は、より強力なデータベース機能を提供する SQLite データベース パッケージであり、複雑なデータを保存する必要があるシナリオに適しています。

まず、sqflite パッケージの依存関係を pubspec.yaml ファイルに追加します。

dependencies:
  flutter:
    sdk: flutter
  sqflite: ^2.0.0+4

次に、sqflite パッケージを使用してデータベースを作成および管理できます。

import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

void main() async {
  final databasePath = await getDatabasesPath();
  final database = await openDatabase(
    join(databasePath, 'app_database.db'),
    version: 1,
    onCreate: (db, version) {
      db.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
    },
  );

  // 插入数据
  await database.insert('users', {'name': 'John'});

  // 查询数据
  final users = await database.query('users');
  for (var user in users) {
    print('User: ${user['name']}');
  }
}

これはデータ永続性の基本的な紹介にすぎません。実際のプロジェクトでは、データベースの移行に対処したり、ORM フレームワークを使用したりする必要がある場合もあります。公式ドキュメントやサンプルコードを読んで、データの永続化についてさらに詳しく学んでいただければ幸いです。

6. 総合的なデモ

以下は、ナビゲーションとルーティング、状態管理、非同期処理、HTTP リクエストと Rest API、データ永続性を含む包括的なサンプル コードです。この例では、プロバイダーを使用して状態を管理し、HTTP リクエスト経由でデータを取得し、SQLite データベースに保存します。

まず、pubspec.yaml ファイルに依存関係を追加します。

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.2
  provider: ^6.0.1
  http: ^0.13.3
  sqflite: ^2.0.0+4

次に、サンプルを構築するために 4 つの Dart ファイルを作成します。

main.dart: MyApp をルート Widget として定義し、MaterialApp を作成します。

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:http/http.dart' as http;

import 'home_page.dart';
import 'user.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MultiProvider(
        providers: [
          ChangeNotifierProvider(create: (context) => UserProvider()),
        ],
        child: HomePage(),
      ),
    );
  }
}

home_page.dart: ユーザー情報を表示するページとして HomePage を定義します。

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import 'user.dart';
import 'second_page.dart';

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final userProvider = Provider.of<UserProvider>(context);
    final users = userProvider.users;

    return Scaffold(
      appBar: AppBar(
        title: Text('User List'),
      ),
      body: ListView.builder(
        itemCount: users.length,
        itemBuilder: (context, index) {
          final user = users[index];
          return ListTile(
            title: Text(user.name),
            subtitle: Text('Email: ${user.email}'),
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => SecondPage(user: user),
                ),
              );
            },
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          await userProvider.fetchUsersFromApi();
        },
        child: Icon(Icons.refresh),
      ),
    );
  }
}

Second_page.dart: SecondPage を 1 人のユーザーに関する情報を表示するページとして定義します。

import 'package:flutter/material.dart';

import 'user.dart';

class SecondPage extends StatelessWidget {
  final User user;

  SecondPage({required this.user});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('User Detail'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(user.name, style: TextStyle(fontSize: 24)),
            SizedBox(height: 10),
            Text('Email: ${user.email}', style: TextStyle(fontSize: 18)),
            SizedBox(height: 10),
            Text('Phone: ${user.phone}', style: TextStyle(fontSize: 18)),
            SizedBox(height: 10),
            Text('Website: ${user.website}', style: TextStyle(fontSize: 18)),
          ],
        ),
      ),
    );
  }
}

user.dart: 状態管理とデータ永続化のために User クラスと UserProvider を定義します。

import 'package:flutter/material.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:http/http.dart' as http;
import 'dart:convert'; // 添加此导入

class User {
  final String name;
  final String email;
  final String phone;
  final String website;

  User(
      {required this.name,
      required this.email,
      this.phone = '',
      this.website = ''});
}

class UserProvider extends ChangeNotifier {
  List<User> _users = [];

  List<User> get users => _users;

  Future<void> fetchUsersFromApi() async {
    final response =
        await http.get(Uri.parse('https://jsonplaceholder.typicode.com/users'));
    if (response.statusCode == 200) {
      final List<dynamic> data = json.decode(response.body); // 使用json.decode方法
      _users = data
          .map((item) => User(
              name: item['name'],
              email: item['email'],
              phone: item['phone'],
              website: item['website']))
          .toList();
      notifyListeners();
      saveUsersToDatabase();
    }
  }

  Future<void> saveUsersToDatabase() async {
    final dbPath = await getDatabasesPath();
    final database = await openDatabase(join(dbPath, 'user_database.db'),
        version: 1, onCreate: (db, version) {
      db.execute(
        'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT, phone TEXT, website TEXT)',
      );
    });

    await database.delete('users');
    for (var user in _users) {
      await database.insert('users', {
        'name': user.name,
        'email': user.email,
        'phone': user.phone,
        'website': user.website
      });
    }
  }

  Future<void> loadUsersFromDatabase() async {
    final dbPath = await getDatabasesPath();
    final database =
        await openDatabase(join(dbPath, 'user_database.db'), version: 1);

    final List<Map<String, dynamic>> maps = await database.query('users');
    _users = maps
        .map((map) => User(
            name: map['name'],
            email: map['email'],
            phone: map['phone'],
            website: map['website']))
        .toList();
    notifyListeners();
  }
}

この例では、HTTP リクエスト経由でユーザー データを取得し、プロバイダーを使用してユーザー データを管理します。ユーザー データは SQLite データベースに保存され、アプリケーションの起動時にデータベースからロードされます。

要約する

この記事では、ナビゲーションとルーティング、状態管理、非同期処理、HTTP リクエストと Rest API、データの永続性など、高度な Flutter トピックを詳しく紹介しました。これらのトピックは実際のアプリケーションにおいて非常に重要であり、より複雑で強力な Flutter アプリケーションを構築するのに役立ちます。

高度な内容を学ぶには継続的な練習と探求が必要ですので、公式ドキュメントやサンプルコードを読んで理解を深め、技術を習得していただければ幸いです。Flutter を使用した学習と開発が進歩し、成功することを願っています。ご質問がございましたら、お気軽にお問い合わせください。全力でお手伝いさせていただきます。

おすすめ

転載: blog.csdn.net/xudepeng0813/article/details/132009629