+7

[Flutter] Quản lý state với Bloc

Quản lý state trong một ứng dụng Flutter là rất quan trọng, việc này ảnh hưởng trực tiếp đến hiệu năng của ứng dụng. Trong bài viết này, chúng ta sẽ cùng nhau tìm hiểu về một thư việc quản lý state là Bloc.

Trước hết chúng ta sẽ xem qua cấu trúc project khi áp dụng thư viện này như sau

Bloc sẽ nhận các event mà UI gửi qua, sau đó xử lý logic (get data, update data, ...) rồi trả về state cho UI. Dựa vào state nhận được từ Bloc, UI sẽ render lại những view cần thiết.

Để đơn giản và dễ hiểu hơn, chúng ta sẽ tạo một project nhỏ là Counter nhé 😄

Thêm thư viện bloc vào project

dependencies:
  bloc: ^3.0.0
  flutter_bloc: ^3.2.0

Các bạn nhớ pub get lại nhé 😄

Áp dụng bloc vào project

Như hình vẽ ban đầu, chúng ta sẽ có 4 thành phần chính: UI, State, Event và Bloc. Chúng ta sẽ lần lượt xây dựng những thành phần này 😄

UI

Trước hết chúng ta sẽ hình dung ứng dụng của mình gồm 1 Text widget, 1 CircularProgressIndicator widget và 1 Button widget như sau.

Text sẽ hiển thị giá trị ban đầu bằng 0. Khi nhấn button (+), sẽ hiển thị loading và sau 1 s, giá trị sẽ được tăng lên 1.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  bool loading = false;

  void _incrementCounter() {
    setState(() {
      loading = true;
    });
    Future.delayed(Duration(seconds: 1), () {
      setState(() {
        _counter++;
        loading = false;
      });
    });
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            loading ? new CircularProgressIndicator() :
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}


State

Ở ví dụ này chúng ta sẽ có 2 trạng thái là LoadingSuccess

class DataState {}

class Loading extends DataState {}

class Success extends DataState {
  int count = 0;
  
  Success(this.count);

}

Event

Trong ví dụ này, đơn giản chúng ta chỉ có duy nhất một event là Increment

class DataEvent {}

class Increment extends DataEvent {}

Bloc

Chúng ta sẽ có giá trị khởi tạo của state là Success(0).

class CounterBloc extends Bloc<DataEvent, DataState> {
  
  // TODO: implement initialState
  DataState get initialState => Success(0);

  
  Stream<DataState> mapEventToState(DataEvent event) {
    // TODO: implement mapEventToState
    throw UnimplementedError();
  }
}

Chúng ta sẽ chuyển phần logic

  void _incrementCounter() {
    setState(() {
      loading = true;
    });
    Future.delayed(Duration(seconds: 1), () {
      setState(() {
        _counter++;
        loading = false;
      });
    });
  }

vào trong bloc như sau

class CounterBloc extends Bloc<DataEvent, DataState> {

  int count = 0;

  
  // TODO: implement initialState
  DataState get initialState => Success(count);

  
  Stream<DataState> mapEventToState(DataEvent event) async* {
    if (event is Increment) {
      yield Loading();
      await Future.delayed(Duration(seconds: 1));
      count++;
      yield Success(count);
    }
  }
}

Update UI

Cuối cùng chúng ta sẽ sửa lại code phần UI để có thể lắng nghe được sự thay đổi của state.

  1. BlocProvider
class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: BlocProvider<CounterBloc>(
        create: (context) => CounterBloc(),
        child: MyHomePage(title: 'Flutter Demo Home Page'),
      ),
    );
  }
}

BlocProvider là một widget cung cấp một bloc để các widget con có thể sử dụng mà ko cần khai báo thông qua BlocProvider.of<T>(context).

  1. BlocBuilder

BlocBuilder là widget được sử dụng bên trong BlocProvider để hứng các sự thay đổi về state của bloc. Nói cách khác, mỗi khi state thay đổi, BlocProvider sẽ thông báo cho các Builder để có thể render lại view theo state. Chúng ta sẽ áp dụng vào project như sau

class MyHomePage extends StatelessWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  
  Widget build(BuildContext context) {
    final CounterBloc counterBloc = BlocProvider.of<CounterBloc>(context);

    return Scaffold(
      appBar: AppBar(
        title: Text(this.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            BlocBuilder<CounterBloc, DataState>(
              builder: (context, state) {
                if (state is Success) {
                  return Text(
                    '${state.count}',
                    style: Theme.of(context).textTheme.headline4,
                  );
                } else {
                  return new CircularProgressIndicator();
                }
              },
            )
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          counterBloc.add(Increment());
        },
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

Chúng ta chạy project và sẽ thấy kết quả giống như ban đầu. Các bạn có thấy đơn giản ko? 😄

Sau khi đã hiểu được cơ bản về bloc, các bạn có thể vào trang chủ của bloc https://bloclibrary.dev/#/gettingstarted để tham khảo thêm các widget mà thư viện flutter_bloc cung cấp, chúng rất có ích khi chúng ta sử dụng.

Cảm ơn các bạn đã đọc bài viết. Happy coding!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí