ตอนที่ 6 ทำแอพ Flutter : การจัดการสถานะ (State Management)

  1. ภาพรวม (Overview) ของการจัดการสถานะ (State) ใน Flutter
  2. Provider
  3. Bloc/Cubit
  4. Redux
  5. ริเวอร์พอด (Riverpod)

การจัดการสถานะ (State Management) เป็นส่วนสำคัญของการรับทำแอพใดๆ เพื่อให้แน่ใจว่าการจัดการข้อมูลที่ราบรื่นและมีประสิทธิภาพ ใน Flutter มีเทคนิคการจัดการสถานะที่หลากหลาย ซึ่งแต่ละเทคนิคมีข้อดีและกรณีการใช้งาน บทความนี้จะสำรวจโซลูชันการจัดการสถานะยอดนิยมใน Flutter ซึ่งรวมถึง Provider, Bloc/Cubit, Redux และ Riverpod เพื่อช่วยให้คุณมีข้อมูลประกอบการตัดสินใจสำหรับการทำแอพของคุณ

1. ภาพรวม (Overview) ของการจัดการสถานะ (State) ใน Flutter

การจัดการสถานะ (State Management) หมายถึงการจัดการข้อมูลและการเปลี่ยนแปลง UI ในลักษณะที่สอดคล้องและมีประสิทธิภาพ ใน Flutter วิดเจ็ตเปลี่ยนรูปไม่ได้ ซึ่งหมายความว่าคุณสมบัติไม่สามารถเปลี่ยนแปลงได้โดยตรง ด้วยเหตุนี้ การจัดการสถานะของแอพจึงมีความสำคัญต่อการแสดงการเปลี่ยนแปลงใน UI เทคนิคการจัดการสถานะที่สำคัญใน Flutter ได้แก่:

  • setState(): เทคนิคการจัดการสถานะโลคัลที่ใช้สำหรับสถานการณ์ง่ายๆ ภายในวิดเจ็ตเดียว
  • InheritedWidget: วิธีเผยแพร่ข้อมูลในแผนผังวิดเจ็ต
  • ScopedModel: แพ็คเกจของบุคคลที่สามที่ให้แนวทางตามแบบจำลอง
  • Provider: โซลูชันการจัดการสถานะยอดนิยมและยืดหยุ่น
  • Bloc/Cubit: แนวทางที่มีประสิทธิภาพสำหรับการทำแอพขนาดใหญ่ โดยแยกตรรกะทางธุรกิจออกจาก UI
  • Redux: คอนเทนเนอร์สถานะที่คาดเดาได้จากการไหลของข้อมูลทิศทางเดียว
  • Riverpod: ทางเลือกที่ทรงพลัง ยืดหยุ่น และปลอดภัยสำหรับ Provider

2. Provider

Provider เป็นโซลูชันการจัดการสถานะที่ได้รับความนิยมและยืดหยุ่นซึ่งสร้างขึ้นจาก InheritedWidget ทำให้กระบวนการเข้าถึงและแก้ไขข้อมูลทั่วทั้งแอพของคุณง่ายขึ้นโดยการเปิดเผยออบเจกต์ไปยังแผนผังวิดเจ็ต Provider มีข้อดีหลายประการ ได้แก่ :

  • ติดตั้งง่ายและรหัสสำเร็จรูปน้อยที่สุด (minimal boilerplate code)
  • ความสามารถในการปรับขนาดสำหรับการใช้งานทั้งขนาดเล็กและขนาดใหญ่
  • การสนับสนุน Dependency injection
หากต้องการใช้ Provider ให้ทำตามขั้นตอนเหล่านี้:
  1. เพิ่มแพ็คเกจ ‘Provider’ ลงใน pubspec.yaml ไฟล์ ของคุณ
  2. สร้างคลาสแบบจำลองเพื่อแสดงข้อมูลของคุณ
  3. ล้อมแผนผังวิดเจ็ตของคุณในไฟล์ ChangeNotifierProvider.
  4. เข้าถึงและ แก้ไขข้อมูลโดยใช้ Provider.of() หรือ Consumer
ตัวอย่าง: การใช้แอพตัวนับโดยใช้ Provider

ในตัวอย่างนี้ เราจะใช้การทำแอพตัวนับอย่างง่ายโดยใช้แพ็คเกจ Provider สำหรับการจัดการสถานะ แอพจะมีปุ่มเพื่อเพิ่มตัวนับและแสดงจำนวนที่อัปเดต

1. เพิ่มแพ็คเกจ ‘provider’ ลงใน pubspec.yaml ไฟล์ของคุณ:

dependencies:
  flutter:
    sdk: flutter
  provider: ^6.0.1

2. สร้างคลาสโมเดล CounterModel ที่ขยาย (extends) ChangeNotifier เพื่อแสดงข้อมูลตัวนับของคุณ:

import 'package:flutter/foundation.dart';

class CounterModel extends ChangeNotifier {
  int _count = 0;

  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

3. ห่อหุ่ม (wrap) วิดเจ็ตของคุณใน ChangeNotifierProvider:

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

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ChangeNotifierProvider(
      create: (context) => CounterModel(),
      child: MaterialApp(
        home: CounterScreen(),
      ),
    );
  }
}

4. เข้าถึงและแก้ไขข้อมูลโดยใช้ Provider.of()หรือ Consumer ใน CounterScreen:

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

class CounterScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Counter App')),
      body: Center(
        child: Consumer<CounterModel>(
          builder: (context, counter, child) {
            return Text(
              'Count: ${counter.count}',
              style: TextStyle(fontSize: 24),
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          Provider.of<CounterModel>(context, listen: false).increment();
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

ในตัวอย่างนี้ เราใช้ ChangeNotifierProvider จัดเตรียมอินสแตนซ์ของ CounterModel ให้กับแผนผังวิดเจ็ต จากนั้นเราใช้ Consumer วิดเจ็ตเพื่อเข้าถึงและแสดงจำนวนปัจจุบัน สุดท้าย เรา.ใช้ Provider.of() เพิ่มจำนวนเมื่อผู้ใช้แตะปุ่ม FloatingActionButton

3. Bloc/Cubit

Bloc (องค์ประกอบตรรกะทางธุรกิจ) และ Cubit เป็นโซลูชันการจัดการสถานะขั้นสูงที่แยกตรรกะทางธุรกิจออกจาก UI พวกเขาบังคับใช้การไหลของข้อมูลทิศทางเดียว ทำให้การทำแอพ Flutter ของคุณสามารถคาดเดาได้มากขึ้นและบำรุงรักษาง่ายขึ้น ความแตกต่างที่สำคัญระหว่าง Bloc และ Cubit คือ:

  • Bloc ขับเคลื่อนด้วยเหตุการณ์ ในขณะที่ Cubit ขับเคลื่อนด้วยฟังก์ชัน
  • Bloc ใช้ Stream สำหรับการเปลี่ยนแปลงสถานะ ในขณะที่ Cubit ใช้ StateNotifier.
หากต้องการใช้ Bloc/Cubit ให้ทำตามขั้นตอนเหล่านี้:
  1. เพิ่มแพ็คเกจ ‘flutter_bloc’ ลงในpubspec.yamlไฟล์ ของคุณ
  2. สร้างคลาส Bloc หรือ Cubit เพื่อจัดการสถานะของแอพ
  3. ใช้ตรรกะในการจัดการเหตุการณ์ (Bloc) หรือฟังก์ชัน (Cubit)
  4. ล้อมแผนผังวิดเจ็ ตของคุณในBlocProviderหรือCubitProvider
  5. เข้าถึงและแก้ไขสถานะโดยใช้BlocBuilder, BlocListener, context.read()หรือ

ตัวอย่าง: การใช้แอพรายการสิ่งที่ต้องทำโดยใช้ Bloc/Cubit

ในตัวอย่างนี้ เราจะใช้การทำแอพรายการสิ่งที่ต้องทำอย่างง่ายโดยใช้แพ็คเกจ Bloc/Cubit สำหรับการจัดการสถานะ แอพจะอนุญาตให้ผู้ใช้เพิ่มงานใหม่และสลับสถานะการเสร็จสิ้นของแต่ละงาน

1. เพิ่มแพ็คเกจ ‘flutter_bloc’ ในpubspec.yamlไฟล์ของคุณ:

dependencies:
  flutter:
    sdk: flutter
  flutter_bloc: ^8.0.1

2. สร้างคลาส Cubit TodoCubitเพื่อจัดการสถานะของแอพของคุณ:

import 'package:bloc/bloc.dart';

class Todo {
  final String title;
  bool isCompleted;

  Todo({required this.title, this.isCompleted = false});
}

class TodoCubit extends Cubit<List<Todo>> {
  TodoCubit() : super([]);

  void addTask(String title) {
    final newTask = Todo(title: title);
    emit([...state, newTask]);
  }

  void toggleTaskCompletion(int index) {
    state[index].isCompleted = !state[index].isCompleted;
    emit([...state]);
  }
}

3. ล้อมแผนผังวิดเจ็ตของคุณใน BlocProvider:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'todo_cubit.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => TodoCubit(),
      child: MaterialApp(
        home: TodoListScreen(),
      ),
    );
  }
}

4. เข้าถึงและแก้ไขสถานะโดยใช้ BlocBuilderและcontext.read() ใน TodoListScreen:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'todo_cubit.dart';

class TodoListScreen extends StatelessWidget {
  final TextEditingController _textController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('To-Do List')),
      body: BlocBuilder<TodoCubit, List<Todo>>(
        builder: (context, todos) {
          return ListView.builder(
            itemCount: todos.length,
            itemBuilder: (context, index) {
              final todo = todos[index];
              return ListTile(
                title: Text(todo.title),
                trailing: Checkbox(
                  value: todo.isCompleted,
                  onChanged: (_) {
                    context.read<TodoCubit>().toggleTaskCompletion(index);
                  },
                ),
              );
            },
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          final title = await _showAddTaskDialog(context);
          if (title != null && title.isNotEmpty) {
            context.read<TodoCubit>().addTask(title);
          }
        },
        child: Icon(Icons.add),
      ),
    );
  }

  Future<String?> _showAddTaskDialog(BuildContext context) {
    return showDialog<String>(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: Text('Add Task'),
          content: TextField(
            controller: _textController,
            decoration: InputDecoration(hintText: 'Task title'),
          ),
          actions: [
            TextButton(
              onPressed: () {
                Navigator.of(context).pop(_textController.text.trim());
              },
              child: Text('Add'),
            ),
            TextButton(
              onPressed: () {
                Navigator.of(context).pop();
              },
              child: Text('Cancel'),
            ),
          ],
        );
      },
    );
  }
}

ในตัวอย่างนี้ เราใช้ BlocProvider เพื่อจัดเตรียมอินสแตนซ์ของ TodoCubit ให้กับแผนผังวิดเจ็ต จากนั้นเราก็ใช้ BlocBuilder วิดเจ็ตเพื่อเข้าถึงและแสดงรายการงาน นอกจากนี้ เรายังใช้context.read() เพื่อเพิ่มงานใหม่และสลับสถานะการเสร็จสิ้นเมื่อผู้ใช้โต้ตอบกับแอพ

ใน TodoListScreen, เราสร้าง ListView เพื่อแสดงงานและใช้ ListTile เพื่อแสดงชื่องานและสถานะความสมบูรณ์ของงานแต่ละรายการ FloatingActionButton เปิดกล่องโต้ตอบเพื่อป้อนชื่องาน และเมื่อผู้ใช้เพิ่มงานใหม่ เราจะเรียกcontext.read<TodoCubit>().addTask(title)ให้อัปเดตสถานะ

ใน _showAddTaskDialog วิธีการ เราสร้าง AlertDialog ด้วย TextField เพื่อให้ผู้ใช้ป้อนชื่องาน ปุ่ม ‘เพิ่ม’ จะเพิ่มงานไปยังรายการสิ่งที่ต้องทำ ในขณะที่ปุ่ม ‘ยกเลิก’ จะยกเลิกกล่องโต้ตอบโดยไม่เพิ่มงานใหม่

ด้วยการใช้การทำแอพ Flutter รายการสิ่งที่ต้องทำโดยใช้แพ็คเกจ Bloc/Cubit เราสามารถแยกตรรกะทางธุรกิจออกจาก UI และรักษาการไหลของข้อมูลทิศทางเดียว ทำให้แอพคาดการณ์ได้มากขึ้นและบำรุงรักษาง่ายขึ้น

4. Redux

Redux เป็นคอนเทนเนอร์สถานะที่คาดเดาได้ซึ่งได้รับแรงบันดาลใจจากสถาปัตยกรรม Flux มันบังคับใช้การไหลของข้อมูลทิศทางเดียวและส่งเสริมกระบวนทัศน์การเขียนโปรแกรมการทำงาน Redux มีข้อดีหลายประการ ได้แก่ :

  • การจัดการของรัฐแบบรวมศูนย์
  • ตรวจแก้จุดบกพร่องและทดสอบได้ง่าย
  • การดีบักการเดินทางข้ามเวลา
หากต้องการใช้ Redux ใน Flutter ให้ทำตามขั้นตอนเหล่านี้:
  1. เพิ่มแพ็คเกจ ‘flutter_redux’ ลงในpubspec.yamlไฟล์ ของคุณ
  2. สร้างร้านค้าเพื่อเก็บสถานะ การดำเนินการ และตัวย่อของแอพ
  3. กำหนดสถานะแอพ การดำเนินการ และตัวลดขนาด
  4. ล้อมแผนผังวิดเจ็ตของคุณในไฟล์StoreProvider.
  5. เข้าถึงและแก้ไขสถานะโดยใช้StoreConnector, store.dispatch(), store.state

ตัวอย่าง: การใช้แอพตะกร้าสินค้าโดยใช้ Redux

ในตัวอย่างนี้ เราจะสาธิตวิธีใช้การทำแอพตะกร้าสินค้าอย่างง่ายโดยใช้ Redux สำหรับการจัดการสถานะใน Flutter

  1. เพิ่ม dependencies

เพิ่ม flutter_redux และ redux แพ็คเกจลงใน pubspec.yaml ไฟล์ของคุณ:

dependencies:
  flutter:
    sdk: flutter
  flutter_redux: ^0.8.2
  redux: ^5.0.0
  1. กำหนดสถานะของแอพ (app state)

สร้าง models โฟลเดอร์และเพิ่ม product.dart ไฟล์สำหรับรุ่นผลิตภัณฑ์:

class Product {
  final String id;
  final String name;
  final double price;

  Product({required this.id, required this.name, required this.price});
}

จากนั้น สร้าง app_state.dart ไฟล์เพื่อกำหนดสถานะของแอพ (app state):

import 'package:shopping_cart/models/product.dart';

class AppState {
  final List<Product> products;

  AppState({required this.products});

  AppState.initialState() : products = List<Product>.unmodifiable([]);
}
  1. กำหนด actions

สร้าง actions โฟลเดอร์และเพิ่ม cart_actions.dart ไฟล์เพื่อกำหนดการดำเนินการของรถเข็น:

import 'package:shopping_cart/models/product.dart';

class AddToCartAction {
  final Product product;

  AddToCartAction({required this.product});
}

class RemoveFromCartAction {
  final Product product;

  RemoveFromCartAction({required this.product});
}
  1. กำหนดตัวลด (reducers)

สร้าง reducers โฟลเดอร์และเพิ่ม cart_reducer.dart ไฟล์เพื่อกำหนดตัวลดรถเข็น:

import 'package:shopping_cart/models/app_state.dart';
import 'package:shopping_cart/actions/cart_actions.dart';

AppState appReducer(AppState state, dynamic action) {
  if (action is AddToCartAction) {
    return AppState(products: [...state.products, action.product]);
  } else if (action is RemoveFromCartAction) {
    return AppState(products: state.products.where((product) => product.id != action.product.id).toList());
  }

  return state;
}
  1. ตั้งค่าร้านค้า Redux

ใน main.dart ไฟล์ของคุณ ให้ตั้งค่าที่เก็บ Redux และรวม MaterialApp วิดเจ็ตของคุณใน StoreProvider:

import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux/redux.dart';
import 'package:shopping_cart/models/app_state.dart';
import 'package:shopping_cart/reducers/cart_reducer.dart';

void main() {
  final store = Store<AppState>(
    appReducer,
    initialState: AppState.initialState(),
  );

  runApp(MyApp(store: store));
}

class MyApp extends StatelessWidget {
  final Store<AppState> store;

  MyApp({required this.store});

  @override
  Widget build(BuildContext context) {
    return StoreProvider<AppState>(
      store: store,
      child: MaterialApp(
        title: 'Shopping Cart',
        theme: ThemeData(primarySwatch: Colors.blue),
        home: ShoppingCart(),
      ),
    );
  }
}
  1. สร้าง UI และเชื่อมต่อกับ Redux

สร้าง shopping_cart.dart ไฟล์เพื่อสร้าง UI ของแอพและเชื่อมต่อกับ Redux โดยใช้ StoreConnector:

import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:shopping_cart/actions/cart_actions.dart';
import 'package:shopping_cart/models/app_state.dart';
import 'package:shopping_cart/models/product.dart';

class ShoppingCart extends StatelessWidget {
  final List<Product> products = [
    Product(id: '1', name: 'Product 1', price: 10.0),
    Product(id: '2', name: 'Product 2', price: 20.0),
    Product(id: '3', name: 'Product 3', price: 30.0),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Shopping Cart')),
      body: ListView.builder(
        itemCount: products.length,
        itemBuilder: (context, index) {
          final product = products[index];
          return ListTile(
            title: Text(product.name),
            subtitle: Text('\$${product.price}'),
            trailing: StoreConnector<AppState, VoidCallback>(
              converter: (store) {
                return () => store.dispatch(AddToCartAction(product: product));
              },
              builder: (_, callback) {
                return IconButton(
                  icon: Icon(Icons.add_shopping_cart),
                  onPressed: callback,
                );
              },
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          showDialog(
            context: context,
            builder: (BuildContext context) {
              return StoreConnector<AppState, List<Product>>(
                converter: (store) => store.state.products,
                builder: (_, cartProducts) {
                  return AlertDialog(
                    title: Text('Your Cart'),
                    content: cartProducts.isNotEmpty
                        ? Column(
                            mainAxisSize: MainAxisSize.min,
                            children: cartProducts.map((product) {
                              return ListTile(
                                title: Text(product.name),
                                subtitle: Text('\$${product.price}'),
                                trailing: StoreConnector<AppState, VoidCallback>(
                                  converter: (store) {
                                    return () => store.dispatch(RemoveFromCartAction(product: product));
                                  },
                                  builder: (_, callback) {
                                    return IconButton(
                                      icon: Icon(Icons.remove_shopping_cart),
                                      onPressed: callback,
                                    );
                                  },
                                ),
                              );
                            }).toList(),
                          )
                        : Center(child: Text('Your cart is empty')),
                    actions: [
                      TextButton(
                        onPressed: () => Navigator.of(context).pop(),
                        child: Text('Close'),
                      ),
                    ],
                  );
                },
              );
            },
          );
        },
        tooltip: 'View Cart',
        child: Icon(Icons.shopping_cart),
      ),
    );
  }
}

แอพตะกร้าสินค้าที่แสดงที่นี่แสดงให้เห็นถึงการใช้ Redux สำหรับการจัดการสถานะใน Flutter โดยจะแสดงรายการสินค้าที่ช่วยให้ผู้ใช้สามารถเพิ่มสินค้าลงในรถเข็นหรือลบออกได้โดยแตะที่ไอคอนที่เกี่ยวข้อง เมื่อกดปุ่มการทำงานแบบลอยตัว รถเข็นจะปรากฏเป็นโมดอล

5. ริเวอร์พอด (Riverpod)

Riverpod เป็นทางเลือกที่ทรงพลัง ยืดหยุ่น และปลอดภัยสำหรับ Provider มีจุดมุ่งหมายเพื่อแก้ไขข้อ จำกัด บางประการของ Provider ในขณะที่เสนอคุณสมบัติเพิ่มเติม เช่น:

  • ปรับปรุงความปลอดภัยเวลาคอมไพล์
  • ไม่จำเป็นต้อง BuildContext เข้าถึง Provider
  • Provider ที่ไม่เปลี่ยนรูปแบบ

หากต้องการใช้ Riverpod ใน Flutter ให้ทำตามขั้นตอนเหล่านี้:

  1. เพิ่มแพ็คเกจ ‘flutter_riverpod’ ลงใน pubspec.yaml ไฟล์ ของคุณ
  2. สร้าง Provider เพื่อจัดการสถานะแอพของคุณ
  3. กำหนดตรรกะสำหรับการแก้ไขและปรับปรุงสถานะ
  4. ล้อมแผนผังวิดเจ็ตของคุณในไฟล์ ProviderScope.
  5. เข้าถึงและแก้ไขสถานะโดยใช้ ref.watch(), ref.read(), ref.listen()

ตัวอย่าง: การใช้แอพยากรณ์อากาศโดยใช้ Riverpod

ในตัวอย่างนี้ เราจะทำแอพสภาพอากาศอย่างง่ายโดยใช้ Riverpod เป็นโซลูชันการจัดการสถานะ แอพจะอนุญาตให้ผู้ใช้ป้อนชื่อเมืองและดึงข้อมูลสภาพอากาศจาก REST API

  1. เพิ่ม dependencies: เพิ่ม dependencies ต่อไปนี้ใน pubspec.yaml ไฟล์ของคุณ:
dependencies:
  flutter:
    sdk: flutter
  http: ^0.13.3
  flutter_riverpod: ^1.0.0
  1. สร้างแบบจำลองสำหรับข้อมูลสภาพอากาศ: สร้าง weather.dart ไฟล์และกำหนด Weather คลาสเพื่อแสดงข้อมูลสภาพอากาศ:
class Weather {
  final String cityName;
  final double temperature;

  Weather({required this.cityName, required this.temperature});

  factory Weather.fromJson(Map<String, dynamic> json) {
    return Weather(
      cityName: json['name'],
      temperature: json['main']['temp'],
    );
  }
}
  1. สร้างคลาส WeatherRepository เพื่อดึงข้อมูลสภาพอากาศ: สร้างweather_repository.dart ไฟล์และกำหนด WeatherRepository คลาส:
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'weather.dart';

class WeatherRepository {
  Future<Weather> fetchWeather(String cityName) async {
    final response = await http.get(Uri.parse(
        'https://api.openweathermap.org/data/2.5/weather?q=$cityName&appid=YOUR_API_KEY&units=metric'));

    if (response.statusCode == 200) {
      return Weather.fromJson(jsonDecode(response.body));
    } else {
      throw Exception('Failed to load weather data');
    }
  }
}

แทนที่ YOUR_API_KEY ด้วยคีย์ OpenWeatherMap API ของคุณ

  1. สร้างผู้ให้บริการ Riverpod: ในmain.dartไฟล์ของคุณ ให้สร้าง a FutureProvider เพื่อจัดการสถานะของข้อมูลสภาพอากาศ:
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'weather_repository.dart';

final weatherProvider = FutureProvider.family<Weather, String>((ref, cityName) async {
  final weatherRepository = WeatherRepository();
  return await weatherRepository.fetchWeather(cityName);
});
  1. สร้าง UI: อัปเดต main.dart ไฟล์เพื่อสร้าง UI สำหรับการทำแอพสภาพอากาศ:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() {
  runApp(ProviderScope(child: WeatherApp()));
}

class WeatherApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: Text('Riverpod Weather App')),
        body: Center(child: WeatherInput()),
      ),
    );
  }
}

class WeatherInput extends StatefulWidget {
  @override
  _WeatherInputState createState() => _WeatherInputState();
}

class _WeatherInputState extends State<WeatherInput> {
  TextEditingController _cityNameController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        TextField(
          controller: _cityNameController,
          decoration: InputDecoration(labelText: 'City Name'),
        ),
        ElevatedButton(
          onPressed: () => context.refresh(weatherProvider(_cityNameController.text)),
          child: Text('Get Weather'),
        ),
        Consumer(builder: (context, watch, _) {
          final weatherAsyncValue = watch(weatherProvider(_cityNameController.text));
          return weatherAsyncValue.map(
            data: (_) => Text(''),
            loading: (_) => CircularProgressIndicator(),
            error: (err) => Text(err.error.toString()),
          );
        }),
      ],
    );
  }
}

ในตัวอย่างนี้ เราทำแอพสภาพอากาศอย่างง่ายโดยใช้ Riverpod เป็นโซลูชั่นการจัดการของรัฐ แอพนี้อนุญาตให้ผู้ใช้ป้อนชื่อเมืองและดึงข้อมูลสภาพอากาศจาก REST API โดยใช้WeatherRepositoryคลาส สถานะของข้อมูลสภาพอากาศได้รับการจัดการโดยการFutureProviderโทร weatherProviderUI สร้างขึ้นโดยใช้StatefulWidgetการเรียกWeatherInputซึ่งรวมถึง a TextFieldสำหรับผู้ใช้อินพุตและ an ElevatedButtonเพื่อทริกเกอร์การดึงข้อมูล วิดConsumerเจ็ตจะรับฟังการเปลี่ยนแปลงในweatherProviderและอัปเดต UI ตามลำดับ โดยแสดงตัวบ่งชี้ความคืบหน้าขณะโหลดและข้อมูลที่ดึงมาหรือข้อความแสดงข้อผิดพลาดหากมีปัญหาใดๆ เกิดขึ้น

การจัดการสถานะเป็นทักษะที่จำเป็นสำหรับนักพัฒนา Flutter ทุกคน เมื่อทำความเข้าใจและเลือกเทคนิคที่เหมาะสมสำหรับการทำแอพของคุณ คุณจะมั่นใจได้ถึงการจัดการข้อมูลที่มีประสิทธิภาพและประสบการณ์การใช้งานที่ราบรื่น Provider, Bloc/Cubit, Redux และ Riverpod เป็นตัวเลือกยอดนิยม โดยแต่ละตัวเลือกมีจุดเด่นและรูปแบบการใช้งานที่แตกต่างกัน เลือกสิ่งที่เหมาะสมกับความต้องการและความซับซ้อนของการทำแอพ Flutter ของคุณมากที่สุด

พัฒนาแอพด้วย Flutter

ตอนที่ 5 ทำแอพ Flutter : การนำทาง (Navigation) และการกำหนดเส้นทาง (Routing)
ตอนที่ 7 ทำแอพ Flutter : ระบบเครือข่าย (Networking) และข้อมูล