In the job_details.dart file I want to listen to changes in the timestamp field when the start time or end time button is pressed and add the corresponding table row. But currently, I am getting value for my current job by listening to changes in my Jobs class and fetching the particular job using its ID. Therefore a new Table row does not get rendered when I tap on the Start time or End time button. How do I implement the above scenario to listen to changes in my Job class whenever a new timestamp is added to the _timestamps list and render a new Table row correspondingly?
job.dart
class Job with ChangeNotifier{
final int jobID;
final String name;
final String startTime = DateTime.now().toString();
String expectedDeliveryDate;
final List<String> _progressImagesUrl = [];
bool isCompleted = false;
final List<Map<String, DateTime?>> _timestamps = [];
Job({required this.jobID, required this.name, this.expectedDeliveryDate = ""});
void editExpectedDeliveryDate(DateTime dt){
expectedDeliveryDate = dt.toString();
notifyListeners();
}
List<String> get progressImagesUrl{
return [..._progressImagesUrl];
}
List<Map<String, DateTime?>> get timestamps{
return [..._timestamps];
}
void addStartTime(){
if(_timestamps.isNotEmpty && _timestamps.last["endTime"] == null){
print("error");
throw Exception("Provide end time first");
}
print("added start time");
_timestamps.add({"startTime": DateTime.now(), "endTime": null});
notifyListeners();
}
void addEndTime(){
if(_timestamps.isEmpty || _timestamps.last["endTime"] != null){
throw Exception("Provide start time first");
}
print("added end time");
_timestamps.last["endTime"] = DateTime.now();
notifyListeners();
}
void addImage(String imageUrl){
_progressImagesUrl.add(imageUrl);
notifyListeners();
}
void toggleIsCompleted(){
isCompleted != isCompleted;
notifyListeners();
}
}
jobs.dart
import 'package:flutter/material.dart';
import 'job.dart';
class Jobs with ChangeNotifier{
final List<Job> _items = [
Job(
jobID: 1,
name: 'firstProduct',
expectedDeliveryDate: DateTime.now().toString(),
),
Job(
jobID: 2,
name: 'second product',
expectedDeliveryDate: DateTime.now().toString(),
),
Job(
jobID: 3,
name: 'third product',
expectedDeliveryDate: DateTime.now().toString(),
),
Job(
jobID: 4,
name: 'fourth product',
expectedDeliveryDate: DateTime.now().toString(),
),
];
List<Job> get items{
return [..._items];
}
void addJob(String name, String endTime){
Job newJob = Job(jobID: name.length, name: name, expectedDeliveryDate: endTime);
_items.add(newJob);
notifyListeners();
}
Job findById(int id){
return _items.firstWhere((element) => element.jobID == id);
}
List<Job> get completedJobs{
return _items.where((element) => element.isCompleted).toList();
}
}
job_details.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:rupa_creation/provider/jobs.dart';
import '../provider/job.dart';
class JobDetailsScreen extends StatelessWidget {
const JobDetailsScreen({Key? key}) : super(key: key);
static const routeName = '/product-detail';
@override
Widget build(BuildContext context) {
final jobId = ModalRoute.of(context)?.settings.arguments as int;
final loadedJob = Provider.of<Jobs>(
context,
).findById(jobId);
List<TableRow> createTimeStampTable(List<Map<String, DateTime?>> items) {
List<TableRow> itemProperties = [];
itemProperties.add(const TableRow(children: [
TableCell(child: Text("Start Time")),
TableCell(child: Text("End Time"))
]));
for (int i = 0; i < items.length; ++i) {
itemProperties.add(TableRow(children: [
TableCell(child: Text("${items[i]["startTime"]?.hour}:${items[i]["startTime"]?.minute}")),
TableCell(child: Text("${items[i]["endTime"]?.hour}:${items[i]["endTime"]?.minute}")),
]));
}
return itemProperties;
}
return Scaffold(
appBar: AppBar(
title: Text(loadedJob.name),
),
body: Container(
child: Column(
children: [
Row(
children: [
TextButton(
onPressed: () {
try {
loadedJob.addStartTime();
// Navigator.of(context).pushReplacementNamed(JobDetailsScreen.routeName, arguments: jobId);
} on Exception catch (e) {
AlertDialog(
title: Text(e.toString()),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Container(
color: Colors.green,
padding: const EdgeInsets.all(14),
child: const Text("okay"),
),
),
],
);
}
},
child: const Text("Start")),
TextButton(
onPressed: () {
try {
loadedJob.addEndTime();
// Navigator.of(context).pushReplacementNamed(JobDetailsScreen.routeName, arguments: jobId);
} on Exception catch (e) {
print("here");
AlertDialog(
title: Text(e.toString()),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Container(
color: Colors.green,
padding: const EdgeInsets.all(14),
child: const Text("okay"),
),
),
],
);
}
},
child: const Text("Stop")),
],
),
Table(
children: createTimeStampTable(loadedJob.timestamps),
)
],
),
),
);
}
}
job_overview.dart
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:rupa_creation/provider/job.dart';
import '../screens/job_details.dart';
class JobOverview extends StatelessWidget {
const JobOverview({
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final job = Provider.of<Job>(context);
DateTime startDateTime = DateTime.parse(job.startTime);
DateTime endDateTime = DateTime.parse(job.expectedDeliveryDate);
return Card(
child: ListTile(
tileColor: Colors.black12,
// contentPadding: EdgeInsets.all(10),
leading: Icon(Icons.ac_unit),
title: Text(job.name, textScaleFactor: 1.2,),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Start Date: ${DateFormat("dd-MM-yyyy").format(startDateTime)}"),
Text("Expected Delivery Date: ${DateFormat("dd-MM-yyyy").format(endDateTime)}"),
],
),
onTap: (){
Navigator.of(context).pushNamed(JobDetailsScreen.routeName, arguments: job.jobID);
},
),
);
}
}
You need to listen your Job class with a ChangeNotifierProvider.value
:
class JobDetailsScreen extends StatelessWidget {
const JobDetailsScreen({Key? key}) : super(key: key);
static const routeName = '/product-detail';
@override
Widget build(BuildContext context) {
final jobId = ModalRoute.of(context)?.settings.arguments as int;
/// Here you search for your jobId but don't listen for changes of that class
final loadedJob = Provider.of<Jobs>(
context,
).findById(jobId);
List<TableRow> createTimeStampTable(List<Map<String, DateTime?>> items) {
List<TableRow> itemProperties = [];
itemProperties.add(const TableRow(children: [
TableCell(child: Text("Start Time")),
TableCell(child: Text("End Time"))
]));
for (int i = 0; i < items.length; ++i) {
itemProperties.add(TableRow(children: [
TableCell(child: Text("${items[i]["startTime"]?.hour}:${items[i]["startTime"]?.minute}")),
TableCell(child: Text("${items[i]["endTime"]?.hour}:${items[i]["endTime"]?.minute}")),
]));
}
return itemProperties;
}
return Scaffold(
appBar: AppBar(
title: Text(loadedJob.name),
),
body: Container(
child: Column(
children: [
Row(
children: [
TextButton(
onPressed: () {
try {
loadedJob.addStartTime();
// Navigator.of(context).pushReplacementNamed(JobDetailsScreen.routeName, arguments: jobId);
} on Exception catch (e) {
AlertDialog(
title: Text(e.toString()),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Container(
color: Colors.green,
padding: const EdgeInsets.all(14),
child: const Text("okay"),
),
),
],
);
}
},
child: const Text("Start")),
TextButton(
onPressed: () {
try {
loadedJob.addEndTime();
// Navigator.of(context).pushReplacementNamed(JobDetailsScreen.routeName, arguments: jobId);
} on Exception catch (e) {
print("here");
AlertDialog(
title: Text(e.toString()),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Container(
color: Colors.green,
padding: const EdgeInsets.all(14),
child: const Text("okay"),
),
),
],
);
}
},
child: const Text("Stop")),
],
),
/// With this you subscribe to changes of that job and listen in the builder
ChangeNotifier<Job>.value(
value: loadedJob,
builder: (context, _) {
final job = context.watch<Job>();
return Table(
children: createTimeStampTable(job.timestamps),
);
}
),
],
),
),
);
}
}
With your current implementation wrapping the table with a ChangeNotifier
is enough to listen to it, if you need the buttons to listen too then you must move up the ChangeNotifierProvider.value
in the widget tree before the body of the Scaffold