我有一个简单的 flutter 应用程序来帮助人们刷牙。两分钟分为用户可以使用 CupertinoPicker
选择的时间间隔。用户对间隔长度的选择使用 SharedPreferences
在应用程序运行之间保存,使用 Provider
传递给小部件。由于 SharedPreferences
的加载是异步发生的,因此我无法使用 FixedExtentScrollController(initialItem: x)
设置起始位置(因为提供程序在构建时尚未读取适当的值。
为了解决这个问题,我可以想到两种方法:
CupertinoPicker
。将回调传递给 Provider
,以便在加载首选项时可以调用 (int chosenTime) {scrollController.jumpToItem(chosenTime);}
。这是我测试过的,它有效,但我认为它不太理想,因为它迫使我将滚动控制器推到应用程序的顶部(下面给出了完整代码)。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']
];