I am using table_calendar flutter package to add a calendar with events. I need to declare a function that uses state from bloc. Is there any problem with his approach? Right now it's working but I feel like there is a better solution that I can't think of.
class TableView extends StatelessWidget {
const TableView({super.key});
#override
Widget build(BuildContext context) {
return BlocBuilder<CalendarBloc, CalendarState>(
builder: (context, state) {
List<Schedule> _getEventsForDay(DateTime day) {
final calendar = state.days.firstWhereOrNull(
(calendar) => day.isOnCalendar(calendar),
);
return calendar == null ? [] : calendar.schedules ?? [];
}
return TableCalendar<Schedule>(
focusedDay: state.focusedDay ?? DateTime.now(),
firstDay: kFirstDay,
lastDay: kLastDay,
selectedDayPredicate: (day) => isSameDay(state.selectedDay, day),
onDaySelected: (selectedDay, focusedDay) {
context.read<CalendarBloc>().add(
DaySelected(
selectedDay: selectedDay,
focusedDay: focusedDay,
),
);
},
eventLoader: _getEventsForDay,
// calendarFormat: CalendarFormat.month,
);
},
);
}
}
Moving the _getEventsForDay function into the CalendarBloc is a good idea as it will make the code easier to test and maintain. The function can be a private method inside the CalendarBloc class. This way, the business logic can be tested in isolation, which will make the tests more reliable and easier to write.
In addition, this also makes the code more modular and separates the presentation logic (the UI) from the business logic (the bloc).
Related
I am still learning how to use cubits and blocs, and I am trying to use a cubit in my project, but I got a little bit confused about how to use it.
There is a screen that requires a phone number and I use the lib "intl_phone_number_input" to format, validate and select the country. When I click the button to the next page it needs to check if the phone is valid, but I need to have a variable that stores this info. The widget InternationalPhoneNumberInput has a property onInputValidated that returns true if the phone number is valid, so where should I create this variable? Should I create it in my widget class or inside the cubit? I created it inside cubit but I am not sure if it is the correct way, so I got this:
onInputValidated: (bool value) {
BlocProvider.of<LoginCubit>(context).isValid =
value;
},
I've studied and seen some examples about cubits and how to use'em but I still didn't get it at all, because in the examples the cubit never used a variable, all variables became a state, but in my case, I need the value as a variable.
I am confused too about how to show a dialog using cubit, I've done it this way:
#override
Widget build(BuildContext context) {
return BlocConsumer<LoginCubit, LoginState>(
listenWhen: (previous, current) => current is ShowDialogErrorLoginState || current is NavigateFromLoginStateToHomePageState,
listener: (context, state) {
if (state is ShowDialogErrorLoginState) {
showErrorDialog(context, state.titleMessage, state.bodyMessage);
}
if (state is NavigateFromLoginStateToHomePageState) {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => const MyHomePage()));
}
},
builder: (context, state) {
if (state is ShowLoginState) {
return buildPhoneForm(context);
}
if (state is SendingCodeLoginState) {
return ProgressView(message: 'Sending SMS code',);
}
if (state is ShowCodeLoginState) {
return buildCodeForm(context);
}
return const ErrorView('Unknown error');
},
);
}
and in my cubit I did the following:
void goToCodeVerification(String phoneNumber) async {
if (!isValid){
String titleMessage = "Phone number invalid";
String bodyMessage = "The given phone number is invalid";
emit(ShowDialogErrorLoginState(titleMessage, bodyMessage));
emit(ShowLoginState());
} else {
emit(SendingCodeLoginState());
// TO DO
// use API to send a code
emit(ShowCodeLoginState());
}
}
Is this the correct way to show a dialog with a cubit?
Ok, so you have a value you want to use, the variable doesn't affect state, and you need to access it inside your cubit.
for something like this, I think storing the variable on the cubit makes the most sense, but keep in mind either approach is acceptable for such a simple case.
I also don't really like how the below code looks:
onInputValidated: (bool value) {
BlocProvider.of<LoginCubit>(context).isValid =
value;
},
it is a bit clunky, I would prefer to move the whole callback into the cubit:
void onInputValidated(bool value) => isValid = value;
that way:
final cubit = BlocProvider.of<LoginCubit>(context);
...
onInputValidated: cubit.onInputValidated,
I'm working on application where I need to persist the selected date range with shared preferences. For date picker I'm using an external package, for state handling I work with provider.
At the beginning in provider model I'm reading the value from shared preferences :
class DateRangeFilterModel extends ChangeNotifier {
PickerDateRange? _dateRange;
DateRangeFilterModel() {
loadDateRange();
}
PickerDateRange? get dateRange => _dateRange;
Future<void> loadDateRange() async {
_dateRange = await SharedPreferencesManager.getFilterDateRange();
notifyListeners();
}
Future<PickerDateRange?> getDateRange() async {
return await SharedPreferencesManager.getFilterDateRange();
}
Future<void> setDateRange(PickerDateRange? dateRange) async {
_dateRange = dateRange;
await SharedPreferencesManager.saveFilterDateRange(
dateRange?.startDate, dateRange?.endDate);
notifyListeners();
}
}
With Consumer widget I try to set up the initial value, for an example I also show the result on Text widget. On Text widget saved date range appears but on SfDateRangePicker not.
Consumer<DateRangeFilterModel>(
builder: (context, model, child) => Column(
children: [
Text(model.dateRange.toString()),
SfDateRangePicker(
initialSelectedRange: model.dateRange,
showTodayButton: false,
enablePastDates: false,
selectionMode: DateRangePickerSelectionMode.range,
minDate: DateTime.now(),
onSelectionChanged: (DateRangePickerSelectionChangedArgs args) {
if (args.value is PickerDateRange) {
final DateTime? startDate = args.value.startDate;
final DateTime? endDate = args.value.endDate;
if (startDate != null && endDate != null) {
context
.read<DateRangeFilterModel>()
.setDateRange(PickerDateRange(startDate, endDate));
}
}
},
),
],
),
),
My ChangeNotifierProvider
ChangeNotifierProvider(
create: (context) => DateRangeFilterModel(),
child: const DateRangePicker()
),
I think there is some concurrency problems. Where I made mistake?
Thanks for any advice.
My solution is was to set initial range through controller.
final DateRangePickerController _controller = DateRangePickerController();
.....
SfDateRangePicker(
initialSelectedRange: _controller.selectedRange = model.dateRange,
controller: _controller,
...
)
I'm trying to make my datepicker reusable, as I need three datepickers, that change different values that are stored in the same class, lets call it _object.date1 , date 2, date 3
To reuse my datepicker I tried the following and passed the variable to the datepicker that shall be changed. But then the field value isn't changed or stored and nothing happens, also no error. If I don't pass the value to _showAndroidDatePicker() and use the line in setState that I commented out below, it works properly. The Datepicker ist linked to the onTap of a TextFormField in a Form.
Can anyone explain to me what I'm missing here? It would be really great to make this reusable.
Many thanks!
void _showAndroidDatePicker(value) {
showDatePicker(
context: context,
builder: (BuildContext context, Widget child) {
return Theme(
data: ThemeData.light().copyWith(
primaryColor: Theme.of(context).primaryColor,
accentColor: Theme.of(context).primaryColor,
colorScheme:
ColorScheme.light(primary: Theme.of(context).primaryColor),
buttonTheme: ButtonThemeData(textTheme: ButtonTextTheme.primary),
),
child: child,
);
},
initialDate: DateTime.now(),
locale: Locale('de'),
firstDate: DateTime(1900),
helpText: 'Bitte wähle ein Datum aus',
lastDate: DateTime.now(),
).then<DateTime>(
(DateTime newDate) {
if (newDate != null) {
setState(() {
value = newDate.toIso8601String();
//Below code works if value isn't passed to datepicker, but I want it variable to avoid boilerplate
// _object.date1 =newDate.toIso8601String();
});
}
return;
},
);
}
Many thanks for your help!
You could pass it the controller and the focus node for the text field. Something like this:
datePickerListener(node, controller) {
if (node.hasFocus) {
node.unfocus();
showDatePicker(your setup).then((date){
var formatter = DateFormat('dd/MM/yyyy');
var formatted = formatter.format(date).toString();
controller.text = formatted;
});
}
}
Then:
FocusNode yourNode = FocusNode();
#override
void initState(){
yourNode.addListener(){
datePickerListener(yourNode, yourController);
};
}
TextFormField(
focusNode: yourNode,
controller: yourController,
)
Something like that. Just adjust to your needs.
After reading a lot about providers, bloc etc. I'm not sure which of this fits the best to certain parts of my app. Let me give an example:
#override
void initState() {
super.initState();
Timer.periodic(new Duration(seconds: 5), (timer) {
periodicUpdate();
});
}
void periodicUpdate() {
setState(() {
isLive = event.IsHappeningNow();
});
}
#override
Widget build(BuildContext context) {
return Column(children: <
Widget>[
EventHeader(isLive: isLive),
EventPhotos(photos: event.photos)
]);
}
In this case EventPhotos will be redrawn every 5 seconds. But how to avoid this? I'm not sure which of the patterns is the best for this usecase. Should I use a StreamProvider for my periodic update and a Consumer inside EventHeader or should I use a ChangeNotifier for my event model?
When it's required to update any specific widget, not entire tree, then Consumer is the best option. Please look at below snippet where you have to add your object which you need to get notified. here in the example is CartModel, replace this with your one and try.
return Consumer<CartModel>(
builder: (context, cart, child) {
return Text("Total price: ${cart.totalPrice}");
},
);
I am working on a flutter app. I have a bunch of times of the day and I want to show an alert notification whenever a time comes and also change the UI of the app if it's running.
So I looked for what options I have, I found the followings
Plugins like flutter-workmanager and background_fetch
Native code implementation with Channels
MY questions are:
Which option is the best for my use case?
Is it better implementing a time counter with delay by duration or using an alarm manager.
How can I pass the data (the current time that comes) between the background task and my app so that I can update the UI?
PS: Currently We are interested in a solution that works at least for Android.
I don't think you need background tasks just to show notifications. Using flutter_local_notifications should be enough for your task. You can schedule a notification with this plugin for specific datetime. Inside of the app, you can use Timer to trigger at specific datetime. I'll show you a simple example:
class _HomePageState extends State<HomePage> {
FlutterLocalNotificationsPlugin notifPlugin;
#override
void initState() {
super.initState();
notifPlugin = FlutterLocalNotificationsPlugin();
}
Future<void> scheduleNotification(DateTime dateTime, String title) async {
final now = DateTime.now();
if (dateTime.isBefore(now)) {
// dateTime is past
return;
}
final difference = dateTime.difference(now);
Timer(difference, () {
showDialog(
context: this.context,
builder: (context) {
return AlertDialog(
content: Text(title),
);
}
);
});
await notifPlugin.schedule(title.hashCode, title, title, dateTime, platformChannelSpecifics, payload: 'test');
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: SafeArea(
child: Container()
),
floatingActionButton: Builder(
builder: (context) {
return FloatingActionButton(
onPressed: () {
final snackbar = SnackBar(content: Text('planned notification'), duration: Duration(seconds: 3));
Scaffold.of(context).showSnackBar(snackbar);
scheduleNotification(DateTime.now().add(Duration(seconds: 2)), 'Hello');
},
);
}
),
);
}
}
But if you need to do some calculations or data fetch then background_fetch is what you need. The only problem with it Apple don't allow background tasks in most cases.