Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make data page loading asynchronous #167

Merged
merged 7 commits into from
Jul 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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