Flutter 之使用 Event Bus 更改主題

Inlight發表於2020-03-08

介紹

隨著每個工程的MVC模組逐漸增多,模組與模組之間的通訊也變得越來越多,程式碼耦合必然增加。

Flutter 之使用 Event Bus 更改主題

Event Bus 為解耦而生,熱別是設計模式為MVCMVP的專案。

Flutter 之使用 Event Bus 更改主題

原始碼

import 'dart:async';

class EventBus {
  StreamController _streamController;

  /// Controller for the event bus stream.
  StreamController get streamController => _streamController;

  /// Creates an [EventBus].
  EventBus({bool sync = false})
      : _streamController = StreamController.broadcast(sync: sync);

  /// Instead of using the default [StreamController] you can use this constructor
  EventBus.customController(StreamController controller)
      : _streamController = controller;

  /// Listens for events of Type [T] and its subtypes.
  Stream<T> on<T>() {
    if (T == dynamic) {
      return streamController.stream;
    } else {
      return streamController.stream.where((event) => event is T).cast<T>();
    }
  }

  /// Fires a new event on the event bus with the specified [event].
  void fire(event) {
    streamController.add(event);
  }

  /// Destroy this [EventBus]. This is generally only in a testing context.
  void destroy() {
    _streamController.close();
  }
}
複製程式碼

是的你沒有看錯,原始碼就只有這些。EventBus之所以這樣簡潔得益於Dart中優秀的StreamStream的用法還是很多的,Event Bus中主要使用Streamlisten()方法。

從原始碼很容易看出Event bus的用法:

初始化

  EventBus({bool sync = false})
      : _streamController = StreamController.broadcast(sync: sync);
複製程式碼

streamController作為Dart官方內建類,實際上就是stream資料的控制器。

sync引數代表事件流是否立刻傳遞到監聽者,預設為false

broadcast這種廣播的初始化方式可以讓stream被多個人訂閱,如果你只想被一個人訂閱請使用StreamController對應single的初始化方式,這裡不過多討論。

EventBus也支援你使用EventBus.customController(StreamController controller)的方式自定義StreamController

監聽

監聽方式為eventBus.on<T>().listen()listen()方法是內建類StreamAPI

  Stream<T> on<T>() {
    if (T == dynamic) {
      return streamController.stream;
    } else {
      return streamController.stream.where((event) => event is T).cast<T>();
    }
  }
複製程式碼
  StreamSubscription<T> listen(void onData(T event),
      {Function onError, void onDone(), bool cancelOnError});
複製程式碼

Event Bus通過泛型過濾,你可以只處理自己定義的事件型別。每當streamController新增你監聽的事件時,監聽回撥將會執行。listen()方法會返回一個StreamSubscription,如果你想取消監聽可以呼叫對應的cancel()方法

觸發事件

  void fire(event) {
    streamController.add(event);
  }
複製程式碼

觸發事件比較簡單,streamController呼叫add()方法給Stream新增事件,新增完成後廣播給所有對應監聽者。

實戰

這裡我們使用Event Bus實現一個變換主題顏色的功能,先看下效果:

Flutter 之使用 Event Bus 更改主題

初始化

dependencies:
  event_bus: ^1.1.1
複製程式碼

新增依賴之後別忘記flutter pub get

Coding

先定義一個主題顏色的事件:

import 'package:flutter/material.dart';

class ThemeColor {
  final Color color;
  
  ThemeColor(this.color);
}
複製程式碼

然後專案首頁初始化一個eventBus,並在initState時監聽ThemeColor事件。

import 'package:flutter/material.dart';
import 'package:event_bus/event_bus.dart';
import 'package:flutterdemo/events/theme_color.dart';

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

EventBus eventBus = EventBus(); // 初始化 EventBus

class MyApp extends StatefulWidget {
  MyApp({Key key}) : super(key: key);

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {

  Color _themeColor;

  @override
  void initState() {
    super.initState();
    // 監聽 ThemeColor 事件
    eventBus.on<ThemeColor>().listen((event) {
      setState(() {
        _themeColor = event.color;
      });
    });
  }

  @override
    Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primaryColor:  _themeColor, // 事件回撥的顏色賦值給 ThemeData
      ),
    );
  }
}
複製程式碼

定義一套主題顏色,在更改顏色時傳送ThemeColor事件。傳送後監聽的回撥方法裡重新設定主題顏色。

import 'package:flutter/material.dart';
import 'package:flutterdemo/components/common_app_bar.dart';
import 'package:flutterdemo/events/theme_color.dart';
import 'package:flutterdemo/main.dart';

class PersonSetting extends StatefulWidget {
  PersonSetting({Key key}) : super(key: key);

  @override
  _PersonSettingState createState() => _PersonSettingState();
}

class _PersonSettingState extends State<PersonSetting> {
  
  Color _themeColor;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: CommonAppBar(title: '設定',),
      body: Center(
        child: DropdownButton(
          value: _themeColor,
          items: <DropdownMenuItem>[
            DropdownMenuItem(value: Color(0xFF2196F3), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFF2196F3),),),),
            DropdownMenuItem(value: Color(0xFFE3F2FD), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFFE3F2FD),),),),
            DropdownMenuItem(value: Color(0xFFBBDEFB), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFFBBDEFB),),),),
            DropdownMenuItem(value: Color(0xFF90CAF9), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFF90CAF9),),),),
            DropdownMenuItem(value: Color(0xFF64B5F6), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFF64B5F6),),),),
            DropdownMenuItem(value: Color(0xFF42A5F5), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFF42A5F5),),),),
            DropdownMenuItem(value: Color(0xFF1E88E5), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFF1E88E5),),),),
            DropdownMenuItem(value: Color(0xFF1976D2), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFF1976D2),),),),
            DropdownMenuItem(value: Color(0xFF1565C0), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFF1565C0),),),),
            DropdownMenuItem(value: Color(0xFF0D47A1), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFF0D47A1),),),),
          ],
          onChanged: (color) {
              eventBus.fire(ThemeColor(color)); // 傳送事件
              setState(() {
                _themeColor = color;
              });
          },
        ),
      ),
    );
  }
}
複製程式碼

其實Event Bus部分的程式碼很少,耦合性也很低,這裡大部分的程式碼都是UI佈局。

總結

Event Bus使用起來很簡單,常規的用法就是定義事件,監聽事件,傳送事件,如何取消監聽上面也已經提到了。當然你也可以自定義streamController做更多的事情,這裡只是拋磚引玉簡單的替換了主題顏色。

當然更改主題顏色使用其他方案也可以實現,如何使用Event Bus大家也可以參考官方的 example,歸根結底都是為了減少程式碼之間的依賴從而降低程式碼的耦合度。

相關文章