加载 SharedPreferences 后更新小部件的最佳方法是什么?

时间:2021-01-16 17:30:20

标签: flutter dart asynchronous flutter-provider

我有一个简单的 flutter 应用程序来帮助人们刷牙。两分钟分为用户可以使用 CupertinoPicker 选择的时间间隔。用户对间隔长度的选择使用 SharedPreferences 在应用程序运行之间保存,使用 Provider 传递给小部件。由于 SharedPreferences 的加载是异步发生的,因此我无法使用 FixedExtentScrollController(initialItem: x) 设置起始位置(因为提供程序在构建时尚未读取适当的值。

为了解决这个问题,我可以想到两种方法:

  1. 在应用的顶层创建一个滚动控制器。将此控制器传递给 CupertinoPicker。将回调传递给 Provider,以便在加载首选项时可以调用 (int chosenTime) {scrollController.jumpToItem(chosenTime);}。这是我测试过的,它有效,但我认为它不太理想,因为它迫使我将滚动控制器推到应用程序的顶部(下面给出了完整代码)。
  2. 创建一个用户无法与之交互的起始屏幕。显示此屏幕时会加载 SharedPreferences,然后在加载完成后导航到主页。我想这对于相对较大的应用程序来说是最好的方法,但对于像这样的单屏应用程序来说感觉有点矫枉过正(我没有测试过这个选项)。

那么,您认为将 SharedPreferences 加载到 Provider 中,然后更新已经绘制的内容(例如 CupertinoPicker)的最佳方法是什么?

提前致谢,我的应用代码如下:

main.dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:toothbrush_timer/constants.dart';
import 'app_prefs.dart';
import 'run_timer.dart';

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

class MyApp extends StatelessWidget {
  final FixedExtentScrollController scrollController =
      FixedExtentScrollController();

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider<AppPrefs>(
          create: (_) => AppPrefs(onReadDone: (int chosenTime) {
            scrollController.jumpToItem(chosenTime);
          }),
        ),
        ChangeNotifierProvider<RunTimer>(
          create: (_) => RunTimer(),
        )
      ],
      child: MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData.dark().copyWith(),
        home: MyHomePage(
          title: 'Toothbrush timer',
          scrollController: scrollController,
        ),
      ),
    );
  }
}

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

  final String title;
  final FixedExtentScrollController scrollController;

  @override
  Widget build(BuildContext context) {
    final bool isRunning = Provider.of<RunTimer>(context).getIsRunning == null
        ? false
        : Provider.of<RunTimer>(context).getIsRunning;
    final int chosenTime = Provider.of<AppPrefs>(context).getChosenTime == null
        ? 0
        : Provider.of<AppPrefs>(context).getChosenTime;
    print('isRunning = $isRunning   Chosen time = $chosenTime');
    return Scaffold(
      appBar: AppBar(
        // Here we take the value from the MyHomePage object that was created by
        // the App.build method, and use it to set our appbar title.
        title: Text(title),
      ),
      body: Center(
        child: Container(
          padding: EdgeInsets.all(50),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                'Total time will be set to 2 minutes, split into intervals',
                textAlign: TextAlign.center,
              ),
              SizedBox(
                height: 20,
              ),
              Text(
                'Interval time',
              ),
              CupertinoPicker(
                itemExtent: 40,
                scrollController: scrollController,
                onSelectedItemChanged: (selectedItem) {
                  print('Calling set interval $selectedItem');
                  Provider.of<AppPrefs>(context, listen: false)
                      .setChosenTime(selectedItem);
                },
                children: kTimeChoices
                    .map((timeChoice) => TextItem(text: timeChoice.toString()))
                    .toList(),
              ),
              Consumer<RunTimer>(builder: (context, timeState, child) {
                return FlatButton(
                  onPressed: () {
                    if (isRunning) {
                      timeState.stop();
                    } else {
                      timeState.start(chosenTime);
                      //scrollController.jumpToItem(0);
                    }
                  },
                  color: isRunning ? Colors.red : Colors.green,
                  child: Text(isRunning ? 'Stop' : 'Start'),
                  shape: RoundedRectangleBorder(
                      borderRadius: BorderRadius.circular(30)),
                );
              }),
              Text(
                isRunning
                    ? Provider.of<RunTimer>(context)
                        .getTimeLeft
                        .toStringAsFixed(1)
                    : kTimeChoices[chosenTime].toStringAsFixed(1),
                style: TextStyle(fontSize: 50, fontWeight: FontWeight.bold),
              ),
              Text(
                'Work on brushing ${isRunning ? Provider.of<RunTimer>(context).getBrushInfo : kBrushList[chosenTime][0]} of your teeth',
                style: TextStyle(fontSize: 30, fontWeight: FontWeight.bold),
                textAlign: TextAlign.center,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class TextItem extends StatelessWidget {
  final text;
  TextItem({this.text});

  @override
  Widget build(BuildContext context) {
    return Text(
      text,
      style: TextStyle(
        fontSize: 30,
        color: Colors.white60,
      ),
    );
  }
}

app_prefs.dart

import 'package:flutter/cupertino.dart';
import 'package:shared_preferences/shared_preferences.dart';

class AppPrefs extends ChangeNotifier {
  SharedPreferences preferences;

  bool _darkMode = true;
  int _chosenTime = 0;
  int _readState = 0;
  Function onReadDone;

  bool get getDarkMode => _darkMode;
  int get getChosenTime => _chosenTime;
  int get getReadState => _readState;

  AppPrefs({this.onReadDone}) {
    readPrefs(onReadDone);
  }

  void setDarkMode(bool darkMode) {
    _darkMode = darkMode;
    writePrefs();
    notifyListeners();
  }

  void setChosenTime(int chosenTime) {
    _chosenTime = chosenTime;
    writePrefs();
    notifyListeners();
  }

  void setReadState(int readState) {
    _readState = readState;
    notifyListeners();
  }

  void readPrefs(readComplete) async {
    preferences = await SharedPreferences.getInstance();
    _darkMode = preferences.getBool('darkMode') ?? true;
    _chosenTime = preferences.getInt('chosenTime') ?? 0;
    _readState = 1;
    print('Preferences are $_darkMode  $_chosenTime');
    readComplete(_chosenTime);
    notifyListeners();
  }

  void writePrefs() {
    preferences.setBool('darkMode', _darkMode);
    preferences.setInt('chosenTime', _chosenTime);
    print('Preferences written $_darkMode  $_chosenTime');
  }
}

run_timer.dart

import 'package:flutter/material.dart';
import 'dart:async';
import 'constants.dart';
import 'dart:math';

class RunTimer extends ChangeNotifier {
  // Private variables
  int _chosenTime = 0;
  int _interval;
  Timer _timer;
  bool _isRunning = false;
  double _timeLeft = 30;
  String _brushInfo = '';
  int _cycleNumber = 0;
  int _maxCycles = 0;

  double get getTimeLeft => _timeLeft;
  String get getBrushInfo => _brushInfo;
  bool get getIsRunning => _isRunning;

  void start(int chosenTime) {
    if (!_isRunning) {
      _isRunning = true;
      _chosenTime = chosenTime;
      _interval = kTimeChoices[_chosenTime];
      _timeLeft = _interval.toDouble();
      print([_chosenTime, _interval, _timeLeft]);
      _maxCycles = kTotalBrushTime ~/ _interval;
      _brushInfo = kBrushList[_chosenTime][_cycleNumber];
      _cycleNumber = 0;
      _timer = Timer.periodic(
        kUpdateInterval,
        (Timer timer) {
          if (_timeLeft <= 0) {
            _cycleNumber += 1;
            if (_cycleNumber < _maxCycles) {
              _timeLeft = _interval.toDouble();
              _brushInfo = kBrushList[_chosenTime][_cycleNumber];
            } else {
              timer.cancel();
              _isRunning = false;
            }
          } else {
            _timeLeft = max(0.0, _timeLeft - 0.1);
          }
          notifyListeners();
        },
      );
    }
  }

  void stop() {
    _timer.cancel();
    _isRunning = false;
    notifyListeners();
  }
}

常量.dart

const kUpdateInterval = const Duration(milliseconds: 100);

const int kTotalBrushTime = 120;
const List<int> kTimeChoices = [20, 30, 40, 60, 120];
const List<List<String>> kBrushList = [
  [
    'the top-left',
    'the top-front',
    'the top-right',
    'the bottom-left',
    'the bottom-front',
    'the bottom-right'
  ],
  ['the top-left', 'the top-right', 'the bottom-left', 'the bottom-right'],
  ['the left', 'the front', 'the right'],
  ['the bottom', 'the top'],
  ['all']
];

0 个答案:

没有答案