Flutter Series Articles - Flutter Advanced 2

In this section, I will introduce you to advanced topics of Flutter in detail, including navigation and routing, state management, asynchronous processing, HTTP requests and Rest API, and data persistence. Let's go through each of these topics one by one.

1. Navigation and Routing

In Flutter, navigation and routing are key concepts for building multi-page applications. Navigation is the process of switching from one page (or routing) to another. Each page corresponds to a Widget. In Flutter, page switching is managed by Navigator.

1.1. Basic Navigation

In Flutter, use MaterialApp to manage the navigation stack. When a new MaterialApp is created, it automatically creates a routing stack and puts a Navigator on top of the stack.

To navigate to a new page, the Navigator.push() method can be used:

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

To return to the previous page, you can use the Navigator.pop() method:

Navigator.pop(context);

1.2. Naming Routes

Flutter also supports named routing, which allows you to use more readable names for navigation in your app. To use named routes, first define the route table in the MaterialApp:

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

You can then use named routes for navigation:

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

1.3. Routing with parameters

Sometimes you need to pass parameters to new pages. In Flutter, you can use ModalRoute.of() to get the parameters in the route:

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

To pass parameters, you can pass in parameters when navigating:

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

1.4. Routing transition animation

Flutter provides a wealth of routing transition animation effects, such as gradient, zoom, pan, etc. You can set PageTransitionsBuilder in MaterialPageRoute to customize transition animation:

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

This is just a basic introduction to navigation and routing. Flutter provides more navigation and routing functions, such as Hero animation, routing interception, etc. You can learn more about navigation and routing in depth by reading the official documentation and sample code.

2. State management

In Flutter, state management is an important aspect of handling shared data and state changes between different pages in an application. There are a variety of state management solutions in Flutter, among which Provider, Riverpod and Bloc are more popular.

2.1. Provider

Provider is a lightweight, easy-to-use state management library. It allows you to share data in the Widget tree and get data through Consumer or Provider.of.

First, create a ChangeNotifierProvider in the root Widget of the application, and put the data model to be shared in it:

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

Then, in the Widget that needs to use data, use Consumer to subscribe to data changes:

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

When the data in CounterModel changes, MyWidget will be updated automatically.

2.2. Riverpod

Riverpod is a new state management library, which is an improved version of Provider. Riverpod offers better performance and a cleaner API.

To use Riverpod, first create a Provider:

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

Then, use a ProviderListener to subscribe to data changes:

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

2.3. Bloc

Bloc is another commonly used state management library that uses unidirectional data flow to manage state. Blocs separate state from operations, making code easier to maintain and test.

First, create a Bloc:

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;
    }
  }
}

Then, in the Widget that needs to use Bloc, use BlocBuilder to subscribe to state changes:

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

This is just a basic introduction to state management, Provider, Riverpod and Bloc all provide more functions and advanced usage. It takes time and practice to learn state management in depth, and you can master more skills and best practices by reading official documents and sample code.

3. Asynchronous processing

In Flutter, asynchronous processing is very common, such as getting data from the network, reading local files, etc. Flutter provides Future and Stream to handle asynchronous operations.

3.1. Future

Future represents an asynchronous operation that may complete or fail. To execute an asynchronous task, use the async and await keywords:

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. Stream

Stream represents a sequence of asynchronous events. Unlike Future, Stream can produce multiple values ​​rather than a single result.

To create a Stream, a StreamController can be used:

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');
  });
}

This is just a basic introduction to asynchronous processing. Flutter also provides more asynchronous tools and functions, such as async* and await for, which can handle asynchronous operations more conveniently. In-depth study of asynchronous processing requires practice and continuous attempts. I hope you can master these technologies in actual projects.

4. HTTP request and Rest API

Interacting with servers is a common requirement in modern applications. Flutter provides a variety of ways to make HTTP requests and handle Rest API.

4.1. Using the http package

Flutter's http package is an easy-to-use HTTP request library that allows you to send HTTP requests and handle responses.

First, add the dependencies of the http package to the pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  http: ^0.13.3

Then, you can use the http package to send HTTP requests:

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. Using the Dio package

dio is another popular HTTP request library that provides richer features and an easy-to-use API.

First, add the dependency of the dio package to the pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  dio: ^4.0.0

Then, you can use the dio package to send HTTP requests:

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. Processing JSON data

Usually the data returned by the server is in JSON format. In Flutter, you can use the dart:convert package to parse and serialize JSON data.

import 'dart:convert';

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

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

This is just a basic introduction to HTTP requests and handling JSON data. In a real project, you may also need to handle errors, use model classes to serialize data, etc. Hope you can learn more about HTTP request and Rest API by studying official documentation and sample code.

5. Data persistence

Data persistence in the application is essential, and Flutter provides a variety of ways to achieve local storage of data.

5.1. Using the shared_preferences package

shared_preferences is an easy-to-use local repository that can store key-value pair data.

First, add the dependency of the shared_preferences package to the pubspec.yaml file:

dependencies:
  flutter:
    sdk: flutter
  shared_preferences: ^2.0.9

Then, the shared_preferences package can be used to read and write data:

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. Using the sqflite package

sqflite is a SQLite database package that provides more powerful database functions and is suitable for scenarios that need to store complex data.

First, add the dependency of the sqflite package to the pubspec.yaml file:

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

Then, the sqflite package can be used to create and manage databases:

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']}');
  }
}

This is just a basic introduction to data persistence. In actual projects, you may also need to deal with database migrations, use ORM frameworks, etc. I hope you can learn more about data persistence by studying official documents and sample codes.

6. Comprehensive Demo

The following is a comprehensive sample code that includes navigation and routing, state management, asynchronous processing, HTTP requests and Rest API, and data persistence. This example will use a Provider to manage state and fetch data via HTTP requests and save it to a SQLite database.

First, add dependencies to the pubspec.yaml file:

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

Then, create four Dart files to build the example:

main.dart: Define MyApp as the root Widget and create 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: Define HomePage as the page to display user information.

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: Defines SecondPage as a page that displays information about a single user.

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: Define the User class and UserProvider for state management and data persistence.

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();
  }
}

This example will fetch user data via HTTP request and use Provider to manage user data. User data will be saved in a SQLite database and loaded from the database when the application is launched.

Summarize

In this article, we introduced advanced Flutter topics in detail, including navigation and routing, state management, asynchronous processing, HTTP requests and Rest API, and data persistence. These topics are very important in practical applications, helping you build more complex and powerful Flutter applications.

Learning advanced topics requires continuous practice and exploration. I hope you can deepen your understanding and master these technologies by reading official documents and sample codes. I wish you progress and success in your learning and development with Flutter! If you have any questions, feel free to ask me, I will try my best to help you out.

Guess you like

Origin blog.csdn.net/xudepeng0813/article/details/132009629