Skip to content

Commit

Permalink
Make data page loading asynchronous (#167)
Browse files Browse the repository at this point in the history
* Make initialisation of data page asynchronous

This will fix a bug where the data page would throw errors
when the user navigated to it before the data was loaded.

* Fixes tests because of the asynchronous init
  • Loading branch information
LarsRefsgaard committed Jul 20, 2023
1 parent 919ed9c commit 34eef32
Show file tree
Hide file tree
Showing 12 changed files with 90 additions and 80 deletions.
12 changes: 6 additions & 6 deletions lib/blocs/app_bloc.dart
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,12 @@ class StudyAppBLoC {
// set up and initialize sensing
await Sensing().initialize();

// add the study and configure sensing
await Sensing().addStudy();

// initialize the UI data models
data.init(Sensing().controller!);

// set up the messaging part
messageManager.initialize().then(
(value) {
Expand All @@ -164,12 +170,6 @@ class StudyAppBLoC {
},
);

// add the study and configure sensing
await Sensing().addStudy();

// initialize the UI data models
data.init(Sensing().controller!);

debug('$runtimeType configuration done.');
_state = StudyAppState.configured;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/main.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 5 additions & 5 deletions lib/ui/cards/heart_rate_card.dart
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@ class HeartRateCardWidgetState extends State<HeartRateCardWidget>
StreamBuilder(
stream: widget.model.heartRateEvents,
builder: (context, AsyncSnapshot<Measurement> snapshot) {
animationController.duration = Duration(
milliseconds: 1000 ~/
(((widget.model.currentHeartRate ?? 0) + 1) / 60));
// animationController.duration = Duration(
// milliseconds: 1000 ~/
// (((widget.model.currentHeartRate ?? 0) + 1) / 60));

return Column(
children: [
Expand Down Expand Up @@ -177,12 +177,12 @@ class HeartRateCardWidgetState extends State<HeartRateCardWidget>
RepaintBoundary(
child: ScaleTransition(
// scale should be _animation if the isOnWrist is true otherwise it should be no scale
scale: !widget.model.contactStatus
scale: widget.model.contactStatus
? Tween<double>(begin: 1, end: 1)
.animate(animationController)
: animation,
child: Icon(
!widget.model.contactStatus
widget.model.contactStatus
? Icons.favorite_outline_rounded
: Icons.favorite_rounded,
color: HeartRateCardWidget.colors[0],
Expand Down
86 changes: 48 additions & 38 deletions lib/ui/pages/data_visualization_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,46 +12,56 @@ class DataVisualizationPage extends StatelessWidget {
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.secondary,
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const CarpAppBar(),
Container(
color: Theme.of(context).colorScheme.secondary,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: Align(
alignment: Alignment.centerLeft,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${locale.translate('pages.data_viz.hello')} ${bloc.friendlyUsername}'
.toUpperCase(),
style: dataCardTitleStyle.copyWith(
color: Theme.of(context).primaryColor),
child: FutureBuilder(
future: bloc.data._dataVisualizationPageViewModel
.init(Sensing().controller!),
builder: (context, data) {
if (!data.hasData) {
return const Center(child: CircularProgressIndicator());
} else {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const CarpAppBar(),
Container(
color: Theme.of(context).colorScheme.secondary,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: Align(
alignment: Alignment.centerLeft,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${locale.translate('pages.data_viz.hello')} ${bloc.friendlyUsername}'
.toUpperCase(),
style: dataCardTitleStyle.copyWith(
color: Theme.of(context).primaryColor),
),
Text(locale.translate('pages.data_viz.thanks'),
style: aboutCardSubtitleStyle),
const SizedBox(height: 15),
],
),
),
Text(locale.translate('pages.data_viz.thanks'),
style: aboutCardSubtitleStyle),
const SizedBox(height: 15),
],
),
),
),
),
),
Expanded(
flex: 4,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: _dataVizCards,
),
),
)
],
Expanded(
flex: 4,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: _dataVizCards,
),
),
)
],
);
}
},
),
),
);
Expand Down
4 changes: 2 additions & 2 deletions lib/view_models/cards/activity_data_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ class ActivityCardViewModel extends SerializableViewModel<WeeklyActivities> {
.where((measurement) => measurement.data is Activity);

@override
void init(SmartphoneDeploymentController ctrl) {
super.init(ctrl);
Future<void> init(SmartphoneDeploymentController ctrl) async {
await super.init(ctrl);

// listen for activity events and count the minutes
activityEvents?.listen((measurement) {
Expand Down
4 changes: 2 additions & 2 deletions lib/view_models/cards/heart_rate_data_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ class HeartRateCardViewModel extends SerializableViewModel<HourlyHeartRate> {
.where((measurement) => measurement.data is PolarHR);

@override
void init(SmartphoneDeploymentController ctrl) {
super.init(ctrl);
Future<void> init(SmartphoneDeploymentController ctrl) async {
await super.init(ctrl);

heartRateEvents?.listen(
(heartRateDataPoint) {
Expand Down
4 changes: 2 additions & 2 deletions lib/view_models/cards/mobility_data_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ class MobilityCardViewModel extends SerializableViewModel<WeeklyMobility> {

MobilityCardViewModel();
@override
void init(SmartphoneDeploymentController ctrl) {
super.init(ctrl);
Future<void> init(SmartphoneDeploymentController ctrl) async {
await super.init(ctrl);

// listen for mobility events and update the features
mobilityEvents?.listen((measurement) {
Expand Down
4 changes: 2 additions & 2 deletions lib/view_models/cards/steps_data_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ class StepsCardViewModel extends SerializableViewModel<WeeklySteps> {
.where((dataPoint) => dataPoint.data is StepCount);

@override
void init(SmartphoneDeploymentController ctrl) {
super.init(ctrl);
Future<void> init(SmartphoneDeploymentController ctrl) async {
await super.init(ctrl);

// listen for pedometer events and count them
pedometerEvents?.listen((pedometerDataPoint) {
Expand Down
11 changes: 6 additions & 5 deletions lib/view_models/data_visualization_page_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,19 @@ class DataVisualizationPageViewModel extends ViewModel {
DataVisualizationPageViewModel();

@override
void init(SmartphoneDeploymentController ctrl) {
Future<int> init(SmartphoneDeploymentController ctrl) async {
super.init(ctrl);
_activityCardDataModel.init(ctrl);
_stepsCardDataModel.init(ctrl);
await _activityCardDataModel.init(ctrl);
await _stepsCardDataModel.init(ctrl);
_measuresCardDataModel.init(ctrl);
_mobilityCardDataModel.init(ctrl);
await _mobilityCardDataModel.init(ctrl);
_surveysCardDataModel.init(ctrl);
_audioCardDataModel.init(ctrl);
_videoCardDataModel.init(ctrl);
_imageCardDataModel.init(ctrl);
_studyProgressCardDataModel.init(ctrl);
_heartRateCardDataModel.init(ctrl);
await _heartRateCardDataModel.init(ctrl);
return 1;
}

/// Delete all persistent view models.
Expand Down
7 changes: 3 additions & 4 deletions lib/view_models/view_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ abstract class SerializableViewModel<D extends DataModel> extends ViewModel {

@override
@mustCallSuper
void init(SmartphoneDeploymentController ctrl) {
Future<void> init(SmartphoneDeploymentController ctrl) async {
super.init(ctrl);
_model = createModel();

// restore the data model (if any saved)
restore().then((savedModel) => _model = savedModel ?? _model);
await restore().then((savedModel) => _model = savedModel ?? _model);

// save the data model on a regular basis.
Timer.periodic(const Duration(minutes: 3), (_) => save(model.toJson()));
Expand Down Expand Up @@ -161,9 +161,8 @@ class CarpStudyAppViewModel extends ViewModel {
_informedConsentViewModel;

@override
void init(SmartphoneDeploymentController ctrl) {
void init(SmartphoneDeploymentController ctrl) async {
super.init(ctrl);
_dataVisualizationPageViewModel.init(ctrl);
_studyPageViewModel.init(ctrl);
_taskListPageViewModel.init(ctrl);
_profilePageViewModel.init(ctrl);
Expand Down
16 changes: 7 additions & 9 deletions test/heart_rate_data_model_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,21 @@ void main() {
setUp(() {
when(mockSmartphoneDeploymentController.measurements)
.thenAnswer((_) => heartRateStreamController.stream);

viewModel.init(mockSmartphoneDeploymentController);
});
tearDownAll(() {
heartRateStreamController.close();
});
group('and update model variables', () {
test('with one event', () async {
viewModel.init(mockSmartphoneDeploymentController);
// Add a heart rate data point to the stream
when(mockPolarHRDatum.hr).thenReturn(80);
when(mockMeasurement.data).thenReturn(mockPolarHRDatum);

heartRateStreamController.sink.add(mockMeasurement);

await Future.delayed(const Duration(seconds: 1));
await Future.delayed(const Duration(milliseconds: 100));
expect(viewModel.currentHeartRate, equals(80.0));
expect(viewModel.dayMinMax, equals(HeartRateMinMaxPrHour(80, 80)));
expect(
Expand All @@ -58,20 +59,19 @@ void main() {
.hourlyHeartRate));
});
test('with multiple events', () async {
viewModel.init(mockSmartphoneDeploymentController);
// Add a heart rate data point to the stream
when(mockPolarHRDatum.hr).thenReturn(90);
when(mockMeasurement.data).thenReturn(mockPolarHRDatum);

heartRateStreamController.sink.add(mockMeasurement);

await Future.delayed(const Duration(seconds: 1));
await Future.delayed(const Duration(milliseconds: 100));
when(mockPolarHRDatum.hr).thenReturn(60);
when(mockMeasurement.data).thenReturn(mockPolarHRDatum);

heartRateStreamController.sink.add(mockMeasurement);

await Future.delayed(const Duration(seconds: 1));
await Future.delayed(const Duration(milliseconds: 100));
expect(viewModel.currentHeartRate, equals(60));
expect(viewModel.dayMinMax, equals(HeartRateMinMaxPrHour(60, 90)));
expect(
Expand All @@ -82,14 +82,13 @@ void main() {
.hourlyHeartRate));
});
test('with events with data that is 0', () async {
viewModel.init(mockSmartphoneDeploymentController);
// Add a heart rate data point to the stream
when(mockPolarHRDatum.hr).thenReturn(0);
when(mockMeasurement.data).thenReturn(mockPolarHRDatum);

heartRateStreamController.sink.add(mockMeasurement);

await Future.delayed(const Duration(seconds: 1));
await Future.delayed(const Duration(milliseconds: 100));
expect(viewModel.currentHeartRate, equals(null));
expect(
viewModel.dayMinMax, equals(HeartRateMinMaxPrHour(null, null)));
Expand All @@ -98,7 +97,6 @@ void main() {
expect(viewModel.contactStatus, equals(false));
});
test('with contactStatus being true', () async {
viewModel.init(mockSmartphoneDeploymentController);
// Add a heart rate data point to the stream
when(mockPolarHRDatum.hr).thenReturn(1);
when(mockPolarHRDatum.contactStatusSupported).thenReturn(true);
Expand All @@ -107,7 +105,7 @@ void main() {

heartRateStreamController.sink.add(mockMeasurement);

await Future.delayed(const Duration(seconds: 1));
await Future.delayed(const Duration(milliseconds: 100));
expect(viewModel.currentHeartRate, equals(anything));
expect(viewModel.dayMinMax, equals(anything));
expect(viewModel.hourlyHeartRate, equals(anything));
Expand Down
10 changes: 6 additions & 4 deletions test/heart_rate_data_model_test.mocks.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Mocks generated by Mockito 5.4.0 from annotations
// Mocks generated by Mockito 5.4.2 from annotations
// in carp_study_app/test/heart_rate_data_model_test.dart.
// Do not manually edit this file.

Expand Down Expand Up @@ -503,13 +503,15 @@ class MockHeartRateCardViewModel extends _i1.Mock
),
) as _i4.HourlyHeartRate);
@override
void init(_i2.SmartphoneDeploymentController? ctrl) => super.noSuchMethod(
_i5.Future<void> init(_i2.SmartphoneDeploymentController? ctrl) =>
(super.noSuchMethod(
Invocation.method(
#init,
[ctrl],
),
returnValueForMissingStub: null,
);
returnValue: _i5.Future<void>.value(),
returnValueForMissingStub: _i5.Future<void>.value(),
) as _i5.Future<void>);
@override
_i5.Future<bool> save(Map<String, dynamic>? json) => (super.noSuchMethod(
Invocation.method(
Expand Down

0 comments on commit 34eef32

Please sign in to comment.