英文:
flutter showCupertinoModalPopup (date picker) with bloc state does not work
问题
I am stuck in a situation where the state is being emitted but the UI is not updating. I have checked the code; it is updating the list.
Cubit class method:
void showDatePicker(context, index) async {
DateTime date = DateTime.now();
await showCupertinoModalPopup(
context: context,
builder: (BuildContext builder) {
return Container(
height: MediaQuery.of(context).copyWith().size.height * 0.25,
color: Colors.white,
child: Column(
children: [
Expanded(
child: CupertinoDatePicker(
mode: CupertinoDatePickerMode.time,
onDateTimeChanged: (value) {
date = value;
},
initialDateTime: DateTime.now(),
),
),
CupertinoButton(
child: const Text('Done'),
onPressed: () {
String formattedTime = DateFormat.jm().format(date);
var updatedList = state.listOfDailyRoutineOption;
updatedList[index].time = formattedTime;
emit(state.copyWith(listOfDailyRoutineOption: updatedList));
Navigator.of(context).pop();
},
)
],
),
);
},
);
}
Cubit state class:
part of 'on_board_cubit.dart';
class OnBoardState extends Equatable {
const OnBoardState({
this.currentPage = 0,
this.listOfDailyRoutineOption = const [],
});
final int currentPage;
final List<DailyRoutineModel> listOfDailyRoutineOption;
@override
List<Object> get props => [currentPage, listOfDailyRoutineOption];
OnBoardState copyWith({
int? currentPage,
List<DailyRoutineModel>? listOfDailyRoutineOption,
}) {
return OnBoardState(
currentPage: currentPage ?? this.currentPage,
listOfDailyRoutineOption:
listOfDailyRoutineOption ?? this.listOfDailyRoutineOption,
);
}
}
UI:
class DailyRouteWidget extends StatefulWidget {
const DailyRouteWidget({Key? key}) : super(key: key);
@override
State<DailyRouteWidget> createState() => _DailyRouteWidgetState();
}
class _DailyRouteWidgetState extends State<DailyRouteWidget> {
@override
void initState() {
BlocProvider.of<OnBoardCubit>(context).onAddDailyRoutine();
super.initState();
}
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return BlocConsumer<OnBoardCubit, OnBoardState>(
listener: (context, state) {
// TODO: implement listener
},
builder: (context, state) {
return Padding(
padding: const EdgeInsets.only(left: 16.0),
child: ListView(children: [
const Text("Daily Routine", style: header3Style),
const SizedBox(height: 20),
const Text("At what time do you do the following:", style: label),
const SizedBox(height: 20),
Column(
children: List.generate(state.listOfDailyRoutineOption.length,
(index) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
state.listOfDailyRoutineOption[index].title,
style: label,
),
Padding(
padding: const EdgeInsets.all(8.0),
child: MyElevatedButton(
borderRadius: 50,
width: size.width * 0.1,
height: size.height * 0.03,
buttonColor: btnColor,
buttonText: state.listOfDailyRoutineOption[index].time,
textStyle: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 13,
),
onPressed: () => BlocProvider.of<OnBoardCubit>(context)
.showDatePicker(context, index),
),
),
],
);
}))
]));
},
);
}
}
The bottom sheet correctly displays the time, and it is set for the daily routine. However, even though it is printed, the UI is not updating. I have also called the emit state in the .then
of showCupertinoBottomSheet
, but the StatefulBuilder
does not work.
UI Screenshots:
UI
Bottom Sheet
英文:
i am stuck in a situtation where the state is being emitted but the UI is not updating i have checked the code it is updating the list.
cubit class method
void showDatePicker(context, index) async {
DateTime date = DateTime.now();
await showCupertinoModalPopup(
context: context,
builder: (BuildContext builder) {
return Container(
height: MediaQuery.of(context).copyWith().size.height * 0.25,
color: Colors.white,
child: Column(
children: [
Expanded(
child: CupertinoDatePicker(
mode: CupertinoDatePickerMode.time,
onDateTimeChanged: (value) {
date = value;
},
initialDateTime: DateTime.now(),
),
),
CupertinoButton(
child: const Text('Done'),
onPressed: () {
String formattedTime = DateFormat.jm().format(date);
var updatedList = state.listOfDailyRoutineOption;
updatedList[index].time = formattedTime;
emit(state.copyWith(listOfDailyRoutineOption: updatedList));
Navigator.of(context).pop();
},
)
],
),
);
},
);
}
cubit state class
part of 'on_board_cubit.dart';
class OnBoardState extends Equatable {
const OnBoardState({
this.currentPage = 0,
this.listOfDailyRoutineOption = const [],
});
final int currentPage;
final List<DailyRoutineModel> listOfDailyRoutineOption;
@override
List<Object> get props => [currentPage, listOfDailyRoutineOption];
OnBoardState copyWith({
int? currentPage,
List<DailyRoutineModel>? listOfDailyRoutineOption,
}) {
return OnBoardState(
currentPage: currentPage ?? this.currentPage,
listOfDailyRoutineOption:
listOfDailyRoutineOption ?? this.listOfDailyRoutineOption,
);
}
}
ui
class DailyRouteWidget extends StatefulWidget {
const DailyRouteWidget({Key? key}) : super(key: key);
@override
State<DailyRouteWidget> createState() => _DailyRouteWidgetState();
}
class _DailyRouteWidgetState extends State<DailyRouteWidget> {
@override
void initState() {
BlocProvider.of<OnBoardCubit>(context).onAddDailyRoutine();
super.initState();
}
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
return BlocConsumer<OnBoardCubit, OnBoardState>(
listener: (context, state) {
// TODO: implement listener
},
builder: (context, state) {
return Padding(
padding: const EdgeInsets.only(left: 16.0),
child: ListView(children: [
const Text("Daily Routine", style: header3Style),
const SizedBox(height: 20),
const Text("At what time do you do the following:", style: label),
const SizedBox(height: 20),
Column(
children: List.generate(state.listOfDailyRoutineOption.length,
(index) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
state.listOfDailyRoutineOption[index].title,
style: label,
),
Padding(
padding: const EdgeInsets.all(8.0),
child: MyElevatedButton(
borderRadius: 50,
width: size.width * 0.1,
height: size.height * 0.03,
buttonColor: btnColor,
buttonText: state.listOfDailyRoutineOption[index].time,
textStyle: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 13,
),
onPressed: () => BlocProvider.of<OnBoardCubit>(context)
.showDatePicker(context, index),
),
),
],
);
}))
]));
},
);
}
}
The bottom sheet popup correctly the time is set for the daily routine even though it is printed but the UI is not updating. i have also called the emit state in .then of showCupertinoBottomSheet
even though the statefulbuilder does not work
答案1
得分: 1
以下是翻译好的部分:
-
不建议在
Widget
外部存储或传递BuildContext
的实例,因为如果与其关联的Widget从Widget树中卸载,它可能会变得无效。请查看BuildContext -
您的cubit依赖于Flutter框架(特别是cupertino)。但是cubit不应该知道任何与框架类(Widget、导航器等)有关的信息,因为它仅包含与应用程序或表示层无关的业务逻辑。
-
您的cubit现在具有多个职责:它不仅管理状态,还开始负责路由和显示UI(showCupertinoModalPopup)。这违反了单一责任原则
在您的特定情况下,有两种可能的变体:
- 在
_DailyRouteWidgetState
中直接显示底部表单作为对MyElevatedButton
的onPressed事件的反应。 - 让您的cubit决定如何对用户的点击事件做出反应。在这种情况下,调用cubit的
onShowDatePickerPressed
,作为反应只发出DisplayDatePicker
或DatePickerRequired
状态,在_DailyRouteWidgetState
内部使用BlocListener
监听该状态,并作为对该状态的反应调用showCupertinoModalPopup
。这是flutter_bloc
库文档中的一个很好的例子。这种实现需要更多的代码,但具有更好的关注点分离,并且允许轻松使用cubit和widget测试覆盖您的逻辑。
英文:
There are several problems with your approach:
-
It's not recommended to store or pass an instance of
BuildContext
outside theWidget
because it may become invalid
if the widget it is associated with is unmounted from the widget tree. Check BuildContext -
Your cubit has dependency on Flutter framework (cupertino in particular). But cubit shouldn't know about any Framework classes (Widgets, Navigators and etc.) at all as it only contains a business logic not related to application or presentation layers.
-
Your cubit has now more than one responsibility: it's not only managing states but also start to be responsible for routing and displaying UI (showCupertinoModalPopup). This breaks single responsibility principle
In your particular case there two variants possible:
- Display bottom sheet in you
_DailyRouteWidgetState
directly as a reaction onMyElevatedButton
onPressed event. - Let your cubit to decide how to react on user tap event. In that case call cubit
onShowDatePickerPressed
and as a reaction just emitDisplayDatePicker
orDatePickerRequired
state, listen that state withBlocListener
inside_DailyRouteWidgetState
and callshowCupertinoModalPopup
as a reaction for that state. Here is a good example fromflutter_bloc
library documentation. This implementation require more code but has a better separation of concerns and allow to easily cover your logic with cubit and widget tests.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论