I am stuck in a weird situation. Have cross-checked all the reference. Problem is my timer is not updating the modelBottomSheet. This is my WorkOutTracker
class WorkOutTracker extends StatefulWidget {
const WorkOutTracker({Key? key}) : super(key: key);
@override
State<WorkOutTracker> createState() => _WorkOutTrackerState();
}
class _WorkOutTrackerState extends State<WorkOutTracker> {
final WorkoutBloc workoutBloc = WorkoutBloc();
String elapsedTime = "0s";
late Timer timer;
void updateElapsedTime(Timer timer) {
workoutBloc.add(StartWorkout(timer: timer));
}
@override
Widget build(BuildContext context) {
return SafeArea(
child: BlocConsumer<WorkoutBloc, WorkoutState>(
bloc: workoutBloc,
listenWhen: (previous, current) => current is WorkoutActionState,
buildWhen: (previous, current) => current is! WorkoutActionState,
listener: (context, state) {
// TODO: implement listener
print("sdsd");
},
builder: (context, state) {
return Scaffold(
body: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
_buildStartWorkoutButton(),
],
),
),
),
if (state is Workingout) _buildResumeCancelButtons(),
],
),
);
},
),
);
}
Widget _buildStartWorkoutButton() {
return Column(
children: [
const Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'Start workout',
style: TextStyle(
color: Colors.black,
fontSize: 25,
fontWeight: FontWeight.bold,
),
),
),
),
Center(
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.blue),
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(6.0)),
),
),
),
child: const Text(
'Start with new routine',
style: TextStyle(color: Colors.black),
),
onPressed: () => _onStartWorkoutButtonPressed(context),
),
),
],
);
}
Widget _buildResumeCancelButtons() {
return Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.blueAccent),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.blue),
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(6.0)),
),
),
),
child: const Text(
'Resume workout',
style: TextStyle(color: Colors.black),
),
onPressed: () => _onResumeWorkoutButtonPressed(context),
),
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.blue),
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(6.0)),
),
),
),
child: const Text(
'Cancel workout',
style: TextStyle(color: Colors.black),
),
onPressed: () => _onCancelWorkoutButtonPressed(),
),
],
),
);
}
void _onStartWorkoutButtonPressed(BuildContext context) {
timer = Timer.periodic(
const Duration(seconds: 1),
(Timer t) {
updateElapsedTime(t);
},
);
showModalBottomSheet<dynamic>(
isScrollControlled: true,
context: context,
builder: (BuildContext context) {
return ExcerciseTracker();
},
);
}
void _onResumeWorkoutButtonPressed(BuildContext context) {
showModalBottomSheet<dynamic>(
isScrollControlled: true,
context: context,
builder: (BuildContext context) {
return ExcerciseTracker();
},
);
}
void _onCancelWorkoutButtonPressed() {
timer.cancel();
workoutBloc.add(EndWorkout());
}
}
Please focus on _onStartWorkoutButtonPressed, here i am starting a timer and showing a modalBottomSheet.
Next file is WorkoutBloc
class WorkoutBloc extends Bloc<WorkoutEvent, WorkoutState> {
WorkoutBloc() : super(WorkoutInitial()) {
on<StartWorkout>(startWorkout);
on<EndWorkout>(endWorkout);
on<ResumeWorkout>(resumeWorkout);
}
FutureOr<void> startWorkout(
StartWorkout event, Emitter<WorkoutState> emit) async {
//starting workout
try {
print(event.timer.tick);
return emit(Workingout(second: event.timer.tick));
} catch (e) {
emit(Error());
}
}
FutureOr<void> endWorkout(
EndWorkout event, Emitter<WorkoutState> emit) async {
try {
return emit(EndWorkingout());
} catch (e) {
emit(Error());
}
}
FutureOr<void> resumeWorkout(
ResumeWorkout event, Emitter<WorkoutState> emit) {}
}
This file is yiedling the Workingout state, on every second change. I have verified it by print statement, and its working fine.
And lastFile
class ExcerciseTracker extends StatefulWidget {
const ExcerciseTracker({super.key});
@override
State<ExcerciseTracker> createState() => _ExcerciseTrackerState();
}
class _ExcerciseTrackerState extends State<ExcerciseTracker> {
final WorkoutBloc workoutBloc = WorkoutBloc();
@override
Widget build(BuildContext context) {
return SizedBox(
height: MediaQuery.of(context).size.height * 0.85,
child: BlocConsumer<WorkoutBloc, WorkoutState>(
bloc: workoutBloc,
listenWhen: (previous, current) => current is WorkoutActionState,
buildWhen: (previous, current) => current is! WorkoutActionState,
listener: (context, state) {
// TODO: implement listener
print("sdsd");
},
builder: (context, state) {
print("BlocConsumer rebuilt with state: $state");
return Column(
children: [
Padding(
padding: const EdgeInsets.all(10.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// Taken from here https://unicode.org/Public/emoji/1.0/emoji-data.txt
const Text(
'\u{1F525} 234 ',
style: TextStyle(
fontSize: 18.0, fontWeight: FontWeight.bold),
),
Text(
_getFormattedElapsedTime(state).toString(),
style: const TextStyle(
fontSize: 18.0, fontWeight: FontWeight.bold),
),
IconButton(
icon: const Icon(
Icons.close,
color: Colors.black,
size: 30.0,
semanticLabel:
'Text to announce in accessibility modes',
),
onPressed: () {
Navigator.pop(context);
},
),
],
),
),
],
);
},
),
);
}
String _getFormattedElapsedTime(WorkoutState state) {
// Calculate elapsed time using the timer
print(state.toString());
int seconds = state is Workingout ? state.second : 10;
int minutes = seconds ~/ 60;
seconds %= 60;
return '$minutes:${seconds.toString().padLeft(2, '0')}';
}
}
This file, is where the problem is, the print(state.toString());
only prints Instance of 'WorkoutInitial'
and its not getting the Workingout state.
Can someone explain to me, why this is happening ?
Thanks.
Edit:-
updated workouttracker code
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_responsive_login_ui/features/Profile/profile_screen.dart';
import 'package:flutter_responsive_login_ui/features/Routine/routine.dart';
import 'package:flutter_responsive_login_ui/features/WorkoutTracker/bloc/workout_bloc.dart';
import 'package:flutter_responsive_login_ui/features/ExcerciseTrack/excercise_track.dart';
class WorkOutTracker extends StatefulWidget {
const WorkOutTracker({Key? key}) : super(key: key);
@override
State<WorkOutTracker> createState() => _WorkOutTrackerState();
}
class _WorkOutTrackerState extends State<WorkOutTracker> {
Timer? _timer;
void updateElapsedTime(Timer timer) {
context.read<WorkoutBloc>().add(StartWorkout(timer: timer));
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => WorkoutBloc(),
child: SafeArea(
child: BlocConsumer<WorkoutBloc, WorkoutState>(
listenWhen: (previous, current) => current is WorkoutActionState,
buildWhen: (previous, current) => current is! WorkoutActionState,
listener: (context, state) {},
builder: (context, state) {
return Scaffold(
body: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
_buildStartWorkoutButton(),
],
),
),
),
if (state is Workingout) _buildResumeCancelButtons(),
],
),
);
},
),
),
);
}
Widget _buildStartWorkoutButton() {
return Column(
children: [
const Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: EdgeInsets.all(8.0),
child: Text(
'Start workout',
style: TextStyle(
color: Colors.black,
fontSize: 25,
fontWeight: FontWeight.bold,
),
),
),
),
Center(
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.blue),
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(6.0)),
),
),
),
child: const Text(
'Start with new routine',
style: TextStyle(color: Colors.black),
),
onPressed: () => _onStartWorkoutButtonPressed(context),
),
),
],
);
}
Widget _buildResumeCancelButtons() {
return Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.blueAccent),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.blue),
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(6.0)),
),
),
),
child: const Text(
'Resume workout',
style: TextStyle(color: Colors.black),
),
onPressed: () => _onResumeWorkoutButtonPressed(context),
),
ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.blue),
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(6.0)),
),
),
),
child: const Text(
'Cancel workout',
style: TextStyle(color: Colors.black),
),
onPressed: () => _onCancelWorkoutButtonPressed(),
),
],
),
);
}
void _onStartWorkoutButtonPressed(BuildContext context) {
_timer?.cancel();
_timer = Timer.periodic(
const Duration(seconds: 1),
(t) async {
updateElapsedTime(t);
},
);
showModalBottomSheet<dynamic>(
isScrollControlled: true,
context: context,
builder: (BuildContext context) {
return BlocProvider.value(
value: context.read<WorkoutBloc>(),
child: ExcerciseTracker(),
);
},
);
}
void _onResumeWorkoutButtonPressed(BuildContext context) {
showModalBottomSheet<dynamic>(
isScrollControlled: true,
context: context,
builder: (BuildContext context) {
return BlocProvider.value(
value: context.read<WorkoutBloc>(),
child: ExcerciseTracker(),
);
},
);
}
void _onCancelWorkoutButtonPressed() {
_timer?.cancel();
context.read<WorkoutBloc>().add(EndWorkout());
}
}
I had a similar issue, an if I remember right, modalBottomSheet or dialog are creating its own context and you cannot access your bloc there. You have to pass the bloc to the modalBottomSheet(e.g to ExcerciseTracker()) as an argument to ensure that you are using same instance of the bloc, otherwise you are creating a new instans of the bloc with the all initial values.
Maybe it can help you.
UPDATED:
Created a demo example. Look at the modal bottom sheet, it has its own context. Some people name it also "context" and then you are getting en error because this context does not provide your bloc, so just replace it with "_" if you don't use it.
and if you are using classes as states, you have to check for a certain state before you can access it. If you are using one class with copyWith method it goes easer.
Here a demo:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
@immutable
abstract class DemoState {}
class DemoInitial extends DemoState {
final String someValue;
DemoInitial(this.someValue);
}
@immutable
abstract class DemoEvent {}
class SetNextStateEvent extends DemoEvent {}
class SetPreviousStateEvent extends DemoEvent {}
class DemoBloc extends Bloc<DemoEvent, DemoState> {
DemoBloc() : super(DemoInitial('Initial value')) {
on<DemoEvent>((event, emit) {
// TODO: implement event handler
});
on<SetNextStateEvent>((event, emit) {
emit(DemoInitial('Next State'));
});
on<SetPreviousStateEvent>((event, emit) {
emit(DemoInitial('Previous State'));
});
}
}
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return Center(
child: BlocProvider(
create: (context) => DemoBloc(),
child: BlocBuilder<DemoBloc, DemoState>(
builder: (context, state) {
if (state is DemoInitial) {
return OutlinedButton(
onPressed: () {
context.read<DemoBloc>().add(SetNextStateEvent());
showModalBottomSheet(
context: context,
builder: (ITS_OWN_CONTEXT) {
return AnotherPageOrWidget(
demoBloc: context.read<DemoBloc>());
});
},
child: Text(state.someValue),
);
} else {
return Container();
}
},
),
),
);
}
}
class AnotherPageOrWidget extends StatelessWidget {
final DemoBloc demoBloc;
const AnotherPageOrWidget({required this.demoBloc, super.key});
@override
Widget build(BuildContext context) {
if (demoBloc.state is DemoInitial) {
return Center(
child: Column(
children: [
OutlinedButton(
onPressed: () {
demoBloc.add(SetPreviousStateEvent());
Navigator.pop(context);
},
child: Text((demoBloc.state as DemoInitial).someValue)),
const SizedBox(
height: 10,
),
OutlinedButton(
onPressed: () {
Navigator.pop(context);
},
child:
const Text('Close bottom sheet without changing the state'))
],
));
} else {
return Center(
child: Text('Error'),
);
}
}
}