Skip to content
This repository has been archived by the owner on Dec 12, 2024. It is now read-only.

load PFIs from known source and cache as shared preference #17

Merged
merged 5 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
7 changes: 7 additions & 0 deletions frontend/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ PODS:
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- web5_flutter (0.0.1):
- Flutter
- webview_flutter_wkwebview (0.0.1):
Expand All @@ -14,6 +17,7 @@ DEPENDENCIES:
- Flutter (from `Flutter`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- web5_flutter (from `.symlinks/plugins/web5_flutter/ios`)
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`)

Expand All @@ -24,6 +28,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_secure_storage/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
web5_flutter:
:path: ".symlinks/plugins/web5_flutter/ios"
webview_flutter_wkwebview:
Expand All @@ -33,6 +39,7 @@ SPEC CHECKSUMS:
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695
web5_flutter: 9d0f3466d7bef47a35cf92aca8dbb30465c63d2e
webview_flutter_wkwebview: 2e2d318f21a5e036e2c3f26171342e95908bd60a

Expand Down
72 changes: 53 additions & 19 deletions frontend/lib/features/pfis/pfi_providers.dart
Original file line number Diff line number Diff line change
@@ -1,22 +1,56 @@
import 'dart:convert';
import 'package:flutter_starter/features/pfis/pfi.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';

final pfisProvider = Provider<List<Pfi>>(
(ref) => [
Pfi(
id: 'prototype',
name: 'Prototype',
didUri: 'did:dht:74hg1efatndi8enx3e4z6c4u8ieh1xfkyay4ntg4dg1w6risu35y',
),
Pfi(
id: 'africa',
name: 'Africa',
didUri: 'coming soon...',
),
Pfi(
id: 'mexico',
name: 'Mexico',
didUri: 'coming soon...',
),
],
);
final pfisProvider = FutureProvider<List<Pfi>>((ref) async {
const url = 'https://raw.githubusercontent.com/TBD54566975/pfi-providers-data/main/pfis.json';
const cacheKey = 'pfis_cache';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

super nit: We can make this private to the file to avoid having to pass it to the _loadFromCache function below.


try {
// Attempt to fetch data from the URL
final response = await http.get(Uri.parse(url));
if (response.statusCode == 200) {
// Parse the JSON data directly into a list of Pfi objects
List<Pfi> pfis = (json.decode(response.body) as List).map((data) {
return Pfi(
id: data['id'] as String,
name: data['name'] as String,
didUri: data['didUri'] as String,
);
}).toList();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With the serialization change below, you can update to this if you wanna:

final pfis = (json.decode(response.body) as List)
          .map<Pfi>((p) => Pfi.fromJson(p))
          .toList();


// Save the data to cache
final prefs = await SharedPreferences.getInstance();
await prefs.setString(cacheKey, response.body);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can add a provider for this as well so that any code can use SharedPreferences without having to get an instance. It will be similar to how we setup secureStorage:
https://github.com/TBD54566975/didpay/blob/main/frontend/lib/services/service_providers.dart#L4

Then initialize and override it in main.dart:
https://github.com/TBD54566975/didpay/blob/main/frontend/lib/main.dart#L13-L24

Then when you need prefs you can use ref.read(sharedPreferencesProvider).setString(...)


return pfis;
} else {
// If server returns an unsuccessful response, try loading from cache
return await _loadFromCache(cacheKey);
}
} catch (e) {
// In case of an error, try loading from cache
return await _loadFromCache(cacheKey);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another approach I like to use on mobile, to keep things snappy is to always load cache first, then always load from network. That way the users gets the list right away from cache and we can refresh from network after. This would require another provider/notifier which is a bit more complicated. Happy to show how that might work as well if you're interested.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea - did that.

});

Future<List<Pfi>> _loadFromCache(String cacheKey) async {
final prefs = await SharedPreferences.getInstance();
String? cachedData = prefs.getString(cacheKey);

if (cachedData != null) {
// If cached data is available, parse and return it
return (json.decode(cachedData) as List).map((data) {
return Pfi(
id: data['id'] as String,
name: data['name'] as String,
didUri: data['didUri'] as String,
);
}).toList();
Comment on lines +43 to +49
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this type of stuff, you can add toJson and fromJson to the type to centralize this logic. Then you can do Pfi.fromJson(data) in these 2 places here. That could might look like this in pfi.dart

  Map<String, dynamic> toJson() {
    return {
      'id': id,
      'name': name,
      'didUri': didUri,
    };
  }

  factory Pfi.fromJson(Map<String, dynamic> json) {
    return Pfi(
      id: json['id'],
      name: json['name'],
      didUri: json['didUri'],
    );
  }

} else {
// If there is no cached data, return an empty list or handle appropriately
return [];
}
}
44 changes: 26 additions & 18 deletions frontend/lib/features/pfis/pfis_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,40 @@ import 'package:flutter_starter/features/pfis/pfi_providers.dart';
import 'package:flutter_starter/features/pfis/pfi_verification_page.dart';
import 'package:flutter_starter/l10n/app_localizations.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:flutter_starter/features/pfis/pfi.dart';

class PfisPage extends HookConsumerWidget {
const PfisPage({super.key});

@override
Widget build(BuildContext context, WidgetRef ref) {
final pfis = ref.watch(pfisProvider);
// Watch the provider and get the AsyncValue object
AsyncValue<List<Pfi>> pfisAsyncValue = ref.watch(pfisProvider);

return Scaffold(
appBar: AppBar(title: Text(Loc.of(context).selectYourRegion)),
body: ListView(
children: [
...pfis.map(
(pfi) => ListTile(
title: Text(pfi.name),
subtitle: Text(pfi.didUri),
trailing: const Icon(Icons.chevron_right),
onTap: () async {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => PfiVerificationPage(pfi: pfi),
),
);
},
),
)
],
body: pfisAsyncValue.when(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: but you can use the ref directly here. This has the advantage that only this part of the widget tree will rebuild when the value changes.

ref.watch(pfisProvider).when(....

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep - did it

loading: () => const Center(child: CircularProgressIndicator()),
error: (error, stack) => Center(child: Text('Error: $error')),
data: (pfis) {
// Build the list when data is available
return ListView(
children: pfis.map(
(pfi) => ListTile(
title: Text(pfi.name),
subtitle: Text(pfi.didUri),
trailing: const Icon(Icons.chevron_right),
onTap: () async {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => PfiVerificationPage(pfi: pfi),
),
);
},
),
).toList(),
);
},
),
);
}
Expand Down
66 changes: 65 additions & 1 deletion frontend/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.0"
file:
dependency: transitive
description:
name: file
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c"
url: "https://pub.dev"
source: hosted
version: "7.0.0"
flutter:
dependency: "direct main"
description: flutter
Expand Down Expand Up @@ -349,6 +357,62 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.4.9"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06"
url: "https://pub.dev"
source: hosted
version: "2.2.1"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
url: "https://pub.dev"
source: hosted
version: "2.3.5"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
sky_engine:
dependency: transitive
description: flutter
Expand Down Expand Up @@ -502,4 +566,4 @@ packages:
version: "1.0.4"
sdks:
dart: ">=3.2.3 <4.0.0"
flutter: ">=3.10.0"
flutter: ">=3.16.0"
1 change: 1 addition & 0 deletions frontend/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ dependencies:
http: ^1.1.2
intl: ^0.18.1
webview_flutter: ^4.4.2
shared_preferences: ^2.2.2

dev_dependencies:
flutter_test:
Expand Down
Loading