I'm doing an APP in FLUTTER and I'm new with it and also new with Dart (I have decent experience with Java and Kotlin), and my problem(question) is this;
I have a list populated with items, when you click in one of the items, it redirects you to another screen. All items leads to virtually the same screen but the screen is individual too for each item in the list.
Inside that screen, i have my timer with a start and a pause button, when you press start it counts from 0 onwards, and when you press pause it pauses the timer and prints the elapsed time in the console (I dont need the elapsed time or the time running in the UI, just in the background and then I save it in a variable)
Here is my problem/question: I can't for the life of me to write the right code so when i click on an item in the list, each screen has its own timer (I can write a shared timer but is not what I want).
Here are my classes (This classes only handle the LOGIC, I call them in other classes to do the UI)
My timer provider
class MyTimerProvider with ChangeNotifier {
DateTime? _startTime;
DateTime? _pauseTime;
int _elapsedTime = 0;
Timer? _timer;
void start() {
_startTime = DateTime.now();
if (_timer == null) {
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
_elapsedTime++;
});
}
}
void pause() {
if (_timer != null) {
_timer!.cancel();
_timer = null;
if (_startTime != null) {
_elapsedTime += DateTime.now().difference(_startTime!).inSeconds;
_startTime = null;
_pauseTime = DateTime.now();
print('Elapsed time: ${elapsedTime.inSeconds} seconds');
}
}
}
void restart() {
_startTime = null;
_pauseTime = null;
_elapsedTime = 0;
if (_timer != null) {
_timer!.cancel();
_timer = null;
}
print('Elapsed time: ${elapsedTime.inSeconds} seconds');
}
Duration get elapsedTime {
if (_startTime == null) {
return Duration(seconds: _elapsedTime);
} else {
return Duration(
seconds:
_elapsedTime + DateTime.now().difference(_startTime!).inSeconds,
);
}
}
}
The PROVIDER to handle the items on the list
class TareaSeleccionada {
final String? id;
final String? descripcion;
TareaSeleccionada({this.id, this.descripcion});
}
class TareasProvider extends ChangeNotifier {
final List<TareaSeleccionada> _tareasSeleccionada = [];
final List<TareaSeleccionada> _tareasCompletadas = [];
List<TareaSeleccionada> get tareasSeleccionada => _tareasSeleccionada;
List<TareaSeleccionada> get tareasCompletadas => _tareasCompletadas;
Future<void> completarTarea(TareaSeleccionada tarea) async {
_tareasSeleccionada.remove(tarea);
_tareasCompletadas.add(tarea);
notifyListeners();
}
Future<List<TareaSeleccionada>> fetchTareasSeleccionada() async {
// Recoger data del back end y popular -> _ordenesSeleccionadas
// Dummy data
final tareasSeleccionadas = [
TareaSeleccionada(id: '1', descripcion: 'Descripción tarea 1'),
TareaSeleccionada(id: '2', descripcion: 'Descripción tarea 2'),
TareaSeleccionada(id: '3', descripcion: 'Descripción tarea 3'),
TareaSeleccionada(id: '4', descripcion: 'Descripción tarea 4'),
TareaSeleccionada(id: '5', descripcion: 'Descripción tarea 5'),
TareaSeleccionada(id: '6', descripcion: 'Descripción tarea 6'),
TareaSeleccionada(id: '7', descripcion: 'Descripción tarea 7'),
TareaSeleccionada(id: '8', descripcion: 'Descripción tarea 8'),
TareaSeleccionada(id: '9', descripcion: 'Descripción tarea 9'),
TareaSeleccionada(id: '10', descripcion: 'Descripción tarea 10'),
TareaSeleccionada(id: '11', descripcion: 'Descripción tarea 11'),
TareaSeleccionada(id: '12', descripcion: 'Descripción tarea 12'),
TareaSeleccionada(id: '13', descripcion: 'Descripción tarea 13'),
];
_tareasSeleccionada.addAll(tareasSeleccionadas);
notifyListeners();
return _tareasSeleccionada;
}
}
The class where I implement the list
class OrdenSeleccionadaBody extends StatelessWidget {
final OrdenSeleccionada ordenSeleccionada;
const OrdenSeleccionadaBody({Key? key, required this.ordenSeleccionada})
: super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 50),
child: Row(
children: [
Expanded(
flex: 2,
child: SingleChildScrollView(
child: Container(
padding: const EdgeInsets.all(50),
child: Text(
'DESCRIPCION ORDEN SELECCIONADA', // tambien se recoge de back end
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
),
),
),
SizedBox(width: 50),
Expanded( //<--------------------- HERE IS THE LIST
flex: 2,
child: Container(
padding: const EdgeInsets.all(50),
child: ListView.builder(
itemCount: 15, // reemplazar con datos de backend
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => TareasOrdenSeleccionada(),
),
);
},
child: ListTile(
title: Text('Tarea Pendiente${index + 1}'),
),
);
},
),
),
),// <----------------------------------- HERE FINISHES THE LIST
Expanded(
flex: 2,
child: Container(
padding: const EdgeInsets.all(20),
child: ListView.builder(
itemCount:
1, // cuando se pulsa completar, la tarea que se selecciono en la la lista de tareas de la orden seleccionada se colocará aqui
itemBuilder: (context, index) {
return ListTile(
title: Text('Tarea Completada ${index + 1}'),
subtitle: Text('Subtexto que sea pertinente'),
);
},
),
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ListaOrdenesScreen(),
),
);
},
child: Icon(Icons.arrow_back),
),
);
}
}
The class that implements the screen where you go after pressing and item on the list
class TareasSeleccionadaBody extends StatelessWidget {
TareasSeleccionadaBody({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
//final tareasSeleccionadaBody = TareasSeleccionadaBody();
final myTimer = Provider.of<MyTimerProvider>(context);
return Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
NavigationbarTareas(),
Expanded(
flex: 2,
child: SingleChildScrollView(
child: Container(
padding: const EdgeInsets.all(20),
child: Text(
'DESCRIPCIÓN TAREA SELECCIONADA',
style: TextStyle(fontSize: 18),
),
),
),
),
Expanded(
flex: 2,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
ElevatedButton(
onPressed: () {
myTimer.start();
},
child: Text('Iniciar'),
),
ElevatedButton(
onPressed: () {
myTimer.pause();
},
child: Text('Pausar'),
),
ElevatedButton(
onPressed: () {
//myTimer.restart();
},
child: Text('Completar'),
),
],
),
),
],
);
}
}
I tried a lot of things (using Future Builder in different places in the code, calling Provider.of<>(context) outside and inside the GestureDetector of the onTap{} and used Future.delayed) which all ended showing me this exception
Exception has occurred.
FlutterError (setState() or markNeedsBuild() called during build.
This _InheritedProviderScope<TareasProvider?> widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was:
_InheritedProviderScope<TareasProvider?>
The widget which was currently being built when the offending call was made was:
OrdenSeleccionadaBody)
The code I put here is in a previous state where the Timer is shared between all items(screens) in the list so it compiles.
What do I need to modify so each TareasOrdenSeleccionada() /the screen/ has its own separate Timer?
Thank you very much.