このセクションでは、ナビゲーションとルーティング、状態管理、非同期処理、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 を使用した学習と開発が進歩し、成功することを願っています。ご質問がございましたら、お気軽にお問い合わせください。全力でお手伝いさせていただきます。