Skip to content

Commit

Permalink
Implement retrieval of historical telemetry data using FutureBuilder
Browse files Browse the repository at this point in the history
- Use stateful widget instead and implement FutureBuilder to retrieve historical telemetry data
- Remove readPreviousReading() method and its related invocation code since the process is automatic
- Add length property for LineChartSpots
  • Loading branch information
rayjasson98 committed Jan 4, 2021
1 parent a448238 commit 83ba600
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 84 deletions.
1 change: 1 addition & 0 deletions lib/data/IoT/models/line_chart_spots.dart
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@ class LineChartSpots {
}
}

int get length => _spots.length;
List<FlSpot> toList() => _spots.toList();
}
43 changes: 11 additions & 32 deletions lib/data/IoT/repositories/telemetry_data_repository.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,38 +22,17 @@ class TelemetryDataRepository {
.map((event) => TelemetryData.fromRealtimeDatabase(event.snapshot));
}

TelemetryData readPreviousReading(String data) {
TelemetryData telemetryData;
_telemetryDb
.child(data)
.orderByKey()
.limitToLast(1)
.onChildAdded
.forEach((event) {
if (event.snapshot != null) {
telemetryData = TelemetryData.fromRealtimeDatabase(event.snapshot);
}
});
return telemetryData;
}
Future<List<TelemetryData>> readPrevReadings(String data, int num) async {
List<TelemetryData> dataList = List();
DataSnapshot snapshot =
await _telemetryDb.child(data).orderByKey().limitToLast(num).once();

List<TelemetryData> readPreviousReadings(String data, int num) {
List<TelemetryData> telemetryDataList = List();
_telemetryDb
.child(data)
.orderByKey()
.limitToLast(num)
.onValue
.forEach((event) {
if (event.snapshot != null && event.snapshot.value != null) {
Map<dynamic, dynamic> map = event.snapshot.value;
map = Map.fromEntries(map.entries.toList()
..sort((e1, e2) => int.parse(e1.key).compareTo(int.parse(e2.key))));
map.forEach((key, value) {
telemetryDataList.add(TelemetryData.from(key, value));
});
}
});
return telemetryDataList;
if (snapshot != null && snapshot.value != null) {
Map<dynamic, dynamic> map = snapshot.value;
map.entries.toList()
..sort((d1, d2) => int.parse(d1.key).compareTo(int.parse(d2.key)))
..forEach((d) => dataList.add(TelemetryData.from(d.key, d.value)));
}
return dataList;
}
}
108 changes: 69 additions & 39 deletions lib/ui/IoT/telemetry_data_chart.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:intl/intl.dart';

class TelemetryDataChart extends StatelessWidget {
class TelemetryDataChart extends StatefulWidget {
TelemetryDataChart({
Key key,
@required this.data,
Expand All @@ -25,58 +25,88 @@ class TelemetryDataChart extends StatelessWidget {
final TelemetryDataCardItem cardItem;

@override
Widget build(BuildContext context) {
TelemetryData telemetryData = context.watch<TelemetryData>();
_TelemetryDataChartState createState() => _TelemetryDataChartState();
}

// If real-time telemetry data is not available,
if (telemetryData == null) {
// Tries to read previous telemetry data.
// Use Provider.of() method due to stricter restrictions imposed by
// context.read() method when it is called inside a build function.
List<TelemetryData> telemetryDataList =
RepositoryProvider.of<TelemetryDataRepository>(
context,
listen: false,
).readPreviousReadings(data, numData);
class _TelemetryDataChartState extends State<TelemetryDataChart> {
bool _hasReadPrev = false;
DateTime _prevTimestamp = DateTime.fromMillisecondsSinceEpoch(0);

// Returns an empty container if previous telemetry data is also not available.
if (telemetryDataList.length == 0) {
return Container();
}
// Adds previous data into line chart points.
else {
telemetryDataList.forEach((telemetryData) {
spots.add(telemetryData.value);
bottomTitles.add(DateFormat.ms().format(telemetryData.timestamp));
});
}
@override
Widget build(BuildContext context) {
// Tries to read previous telemetry data.
if (!_hasReadPrev) {
return FutureBuilder<List<TelemetryData>>(
// Use Provider.of() method due to stricter restrictions imposed by
// context.read() method when it is called inside a build function.
future: RepositoryProvider.of<TelemetryDataRepository>(
context,
listen: false,
).readPrevReadings(widget.data, widget.numData),
builder: (_, snapshot) {
if (snapshot.hasData) {
// Adds previous data into line chart points.
snapshot.data.forEach((data) {
widget.spots.add(data.value);
widget.bottomTitles.add(DateFormat.ms().format(data.timestamp));
// Saves timestamp of last added data
_prevTimestamp = data.timestamp;
});
Future.delayed(Duration.zero, () {
setState(() {
_hasReadPrev = true;
});
});
} else if (snapshot.hasError) {
Future.delayed(Duration.zero, () {
setState(() {
_hasReadPrev = true;
});
});
}
return Container();
},
);
}
// If real-time telemetry data is available, add data into line chart points.
// After finished reading previous data,
else {
spots.add(telemetryData.value);
bottomTitles.add(DateFormat.ms().format(telemetryData.timestamp));
TelemetryData data = context.watch<TelemetryData>();
// Avoids adding duplicate data since the data stream automatically
// fetches the last added data in the first fetch.
if (data != null && data.timestamp != _prevTimestamp) {
widget.spots.add(data.value);
widget.bottomTitles.add(DateFormat.ms().format(data.timestamp));
}

if (widget.spots.length == 0) {
return Container();
} else {
return _buildlineChart();
}
}
}

LineChart _buildlineChart() {
return LineChart(
LineChartData(
lineTouchData: _lineTouchData(),
titlesData: _titlesData(),
lineBarsData: _lineBarsData(spots),
lineBarsData: _lineBarsData(widget.spots),
extraLinesData: ExtraLinesData(
horizontalLines: [
_horizontalLine(cardItem.lowerThreshold),
_horizontalLine(cardItem.upperThreshold),
_horizontalLine(widget.cardItem.lowerThreshold),
_horizontalLine(widget.cardItem.upperThreshold),
],
),
clipData: FlClipData.all(),
gridData: FlGridData(show: false),
borderData: FlBorderData(show: false),
// Decrements 1.0 by 0.15 to avoid points be blocked by the drawing region.
minX: 0.85,
// Increments by 0.15 to avoid points be blocked by the drawing region.
maxX: numData.toDouble() + 0.15,
maxY: cardItem.upperBoundary,
minY: cardItem.lowerBoundary,
// Decrements 1.0 by 0.2 to avoid points be blocked by the drawing region.
minX: 0.8,
// Increments by 0.2 to avoid points be blocked by the drawing region.
maxX: widget.numData.toDouble() + 0.2,
maxY: widget.cardItem.upperBoundary,
minY: widget.cardItem.lowerBoundary,
),
);
}
Expand All @@ -85,7 +115,7 @@ class TelemetryDataChart extends StatelessWidget {
return [
LineChartBarData(
spots: spots.toList(),
showingIndicators: xIndexes,
showingIndicators: widget.xIndexes,
isCurved: true,
curveSmoothness: 0,
colors: const [Colors.white],
Expand Down Expand Up @@ -151,8 +181,8 @@ class TelemetryDataChart extends StatelessWidget {
reservedSize: 10,
margin: 8,
getTitles: (x) {
if (x.ceil() <= bottomTitles.length) {
return bottomTitles[x.ceil() - 1];
if (x.ceil() <= widget.bottomTitles.length) {
return widget.bottomTitles[x.ceil() - 1];
} else {
return '';
}
Expand Down
13 changes: 0 additions & 13 deletions lib/ui/IoT/telemetry_data_reading.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import 'package:farmassist/app_theme.dart';
import 'package:farmassist/data/IoT/models/telemetry_data.dart';
import 'package:farmassist/data/IoT/repositories/telemetry_data_repository.dart';
import 'package:farmassist/ui/IoT/reload_time.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
Expand All @@ -16,18 +15,6 @@ class TelemetryDataReading extends StatelessWidget {
Widget build(BuildContext context) {
TelemetryData telemetryData = context.watch<TelemetryData>();

// If real-time telemetry data is not available,
if (telemetryData == null) {
// Tries to read previous telemetry data.
// Use Provider.of() method due to stricter restrictions imposed by
// context.read() method when it is called inside a build function.
telemetryData = RepositoryProvider.of<TelemetryDataRepository>(
context,
listen: false,
).readPreviousReading(data);
}

// Returns "N/A" if previous telemetry data is also not available.
if (telemetryData == null) {
return _buildReading("N/A");
} else {
Expand Down

0 comments on commit 83ba600

Please sign in to comment.