英文:
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
BuildContextoutside theWidgetbecause 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
_DailyRouteWidgetStatedirectly as a reaction onMyElevatedButtononPressed event. - Let your cubit to decide how to react on user tap event. In that case call cubit
onShowDatePickerPressedand as a reaction just emitDisplayDatePickerorDatePickerRequiredstate, listen that state withBlocListenerinside_DailyRouteWidgetStateand callshowCupertinoModalPopupas a reaction for that state. Here is a good example fromflutter_bloclibrary 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。




评论