Skip to content

Commit

Permalink
getting rid of hydrated stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
Desync-o-tron committed Sep 15, 2024
1 parent d29e59d commit 9a69183
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 204 deletions.
204 changes: 56 additions & 148 deletions lib/cloud_io/firestore_sync.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,61 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/foundation.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:open_fitness_tracker/DOM/training_metadata.dart';
//todo
//turn on auth persistance! firesbase auth
//enable firestore caching on web lul

const historyKey = 'TrainingHistoryCubit';

//todo I wonder if I can use listen to firebase in combination with relying on cache otherwise.

//todo can I make this lazy?
Future<List<TrainingSession>> getUserTrainingHistory({required bool useCache}) async {
// useCache = false;
// todo auth persitance
if (FirebaseAuth.instance.currentUser == null) return Future.error("please sign in");
if (!FirebaseAuth.instance.currentUser!.emailVerified) return Future.error("please verify email");

final FirebaseFirestore firestore = FirebaseFirestore.instance;
CollectionReference users = firestore.collection('users');
DocumentReference userDoc = users.doc(FirebaseAuth.instance.currentUser!.uid);
var cloudTrainingHistory = await userDoc.collection(historyKey).get(GetOptions(
source: useCache ? Source.cache : Source.serverAndCache,
));
// List<Map<String, dynamic>> stringifiedCloudHistory = [];
List<TrainingSession> sessions = [];
for (var doc in cloudTrainingHistory.docs) {
// stringifiedCloudHistory.add(doc.data());
sessions.add(TrainingSession.fromJson(doc.data()));
}
// for (var sesh in stringifiedCloudHistory) {
// sessions.add(TrainingSession.fromJson(sesh));
// }

return sessions;
}

/// will remove the history data corresponding to the id of the training session
// this smells. why do I have two rm calls (look at the caller) also, do I need to rm from the local oldhistory?
Future<void> removeHistoryData(final TrainingSession sesh) async {
if (FirebaseAuth.instance.currentUser == null) return Future.error("please sign in");
if (!FirebaseAuth.instance.currentUser!.emailVerified) return Future.error("please verify email");

if (sesh.id == '') return Future.error("no session id!");

final FirebaseFirestore firestore = FirebaseFirestore.instance;
CollectionReference users = firestore.collection('users');
DocumentReference userDoc = users.doc(FirebaseAuth.instance.currentUser!.uid);
return userDoc.collection(historyKey).doc(sesh.id).delete();
}


////
////
////
////
/*
late FirestoreHydratedStorageSync cloudStorage;
class FirestoreHydratedStorageSync {
Expand Down Expand Up @@ -79,7 +131,7 @@ class FirestoreHydratedStorageSync {
var history = storage.read(historyKey);
if (history == null) return;
var oldHistory = storage.read(historyKey + tokenForLastSync);
// var oldHistory = storage.read(historyKey + tokenForLastSync);
List<Map<String, dynamic>> stringifiedHistory = [];
Expand Down Expand Up @@ -109,8 +161,6 @@ class FirestoreHydratedStorageSync {
// }
}
//turn on auth persistance! firesbase auth

//todo why don't we use firebase offline for this..
//though I want to use a custom offline for exercises, right? theres a ton.
//todo maybe full sync on sign in and use Firestore's onSnapshot method!
Expand Down Expand Up @@ -204,146 +254,4 @@ class FirestoreHydratedStorageSync {
}
}
}

/*
class FirestoreHydratedStorage extends HydratedStorage {
FirestoreHydratedStorage(super.box);
static List<String> dirtyKeys = [];
// static Future<HydratedStorage> build({
// required Directory storageDirectory,
// HydratedCipher? encryptionCipher,
// }) {
// return HydratedStorage.build(storageDirectory: storageDirectory, encryptionCipher: encryptionCipher);
// }
@override
Future<void> write(String key, dynamic value) async {
dirtyKeys.add(key);
return super.write(key, value);
}
@override
Future<void> delete(String key) {
dirtyKeys.add(key);
return super.delete(key);
}
void sync() {
if (!FirebaseAuth.instance.currentUser!.emailVerified) return;
FirebaseFirestore firestore = FirebaseFirestore.instance;
//right now all our keys will be for a collection in the user's document
dirtyKeys.forEach((key) async {
// DocumentSnapshot snapshot = await firestore.collection(collectionPath).doc(key).get();
CollectionReference users = firestore.collection('users');
DocumentReference userDoc = users.doc(FirebaseAuth.instance.currentUser!.uid);
DocumentSnapshot userSnapshot = await userDoc.get();
CollectionReference userAttributeCollection = userSnapshot.reference.collection(key);
userAttributeCollection.add(read(key));
int i = 0;
// userAttributeCollection.doc()
// userAttributeCollection.
// .set({key: read(key)});
/*
await FirebaseFirestore.instance
.collection(collection)
.doc("doc_Id")
.set(data);
*/
});
}
}
*/

// ok we need to use firestore to store data, so we can have a timer that runs in the background and updates firestore with the current data.
// teh local data is always the source of truth, but we can use firestore to store the data and sync it between devices.
// in conjunction with a timer, we can inherit HydratedStorage with a flag that tells us if we have pending changes
// for reads... we can have a listener for firestore that updates the local data when it changes.
// you would think that
/*
class FirestoreHydratedStorage implements Storage {
final FirebaseFirestore firestore;
// late final String collectionPath;
// FirestoreHydratedStorage({FirebaseFirestore? firestore, required this.collectionPath})
// : firestore = firestore ?? FirebaseFirestore.instance;
FirestoreHydratedStorage({FirebaseFirestore? firestore})
: firestore = firestore ?? FirebaseFirestore.instance;
@override
dynamic read(String key) async {
try {
if (FirebaseAuth.instance.currentUser!.emailVerified) {}
// DocumentSnapshot snapshot = await firestore.collection(collectionPath).doc(key).get();
// CollectionReference users = firestore.collection('users');
// QuerySnapshot usersSnapshot = await users.get();
FirebaseFirestore firestore = FirebaseFirestore.instance;
CollectionReference users = firestore.collection('users');
DocumentReference userDoc = users.doc(FirebaseAuth.instance.currentUser!.uid);
DocumentSnapshot userSnapshot = await userDoc.get();
CollectionReference userAttributeCollection = userSnapshot.reference.collection(key);
return userAttributeCollection.get();
// userSnapshot.data()
// user
// firebase.us
// CollectionReference collection = firestore.collection(key);
// QuerySnapshot snapshot = await collection.get();
// snapshot.docs.forEach((doc) {
// print("doc.data():");
// print(doc.data());
// });
// return snapshot.docs;
//todo json stuff!
// return snapshot.
// return snapshot.exists ? snapshot.data() : null;
} catch (e) {
print("Error reading from Firestore: $e");
return null;
}
}
@override
Future<void> write(String key, dynamic value) async {
try {
// await firestore.collection(collectionPath).doc(key).set(value);
} catch (e) {
print("Error writing to Firestore: $e");
}
}
@override
Future<void> delete(String key) async {
try {
// await firestore.collection(collectionPath).doc(key).delete();
} catch (e) {
print("Error deleting from Firestore: $e");
}
}
@override
Future<void> clear() async {
try {
// Firestore does not support clearing a collection via a single operation in client-side code.
// You would need to delete documents individually or use a server-side solution like Cloud Functions.
print(
"FirestoreStorage.clear() is not supported directly. Consider implementing a server-side solution.");
} catch (e) {
print("Error clearing Firestore collection: $e");
}
}
@override
Future<void> close() {
// TODO: implement close
throw UnimplementedError();
}
}
*/
*/
61 changes: 25 additions & 36 deletions lib/history/history_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ class HistoryPage extends StatelessWidget {

@override
Widget build(BuildContext context) {
var history = context.watch<TrainingHistoryCubit>().state;
return Scaffold(
appBar: AppBar(
title: const Text('History'),
Expand All @@ -96,30 +95,26 @@ class HistoryPage extends StatelessWidget {
],
),
body: Container(
color: Colors.blueGrey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (history.isEmpty)
const Padding(
padding: EdgeInsets.symmetric(vertical: 12.0),
child: Text(
'No History',
style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold),
),
),
Expanded(
child: ListView.builder(
itemCount: history.length,
// itemCount: 10,
itemBuilder: (context, index) {
return TrainingSessionHistoryCard(session: history[index]);
},
),
),
],
),
),
color: Colors.blueGrey,
child: FutureBuilder<List<TrainingSession>>(
future: getUserTrainingHistory(useCache: true),
builder: (BuildContext context, AsyncSnapshot<List<TrainingSession>> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return Center(child: Text('Error: ${snapshot.error}'));
} else if (!snapshot.hasData || snapshot.data!.isEmpty) {
return const Center(child: Text('No History'));
} else {
return ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (context, index) {
return TrainingSessionHistoryCard(session: snapshot.data![index]);
},
);
}
},
)),
);
}

Expand Down Expand Up @@ -149,10 +144,11 @@ class HistoryPage extends StatelessWidget {
child: const Text("dont click me"),
)),
PopupMenuItem<String>(
value: 'resync history',
value: 'resync history w/ server',
child: ElevatedButton(
onPressed: () {
cloudStorage.fetchHistoryData();
// have this do a pull down?
getUserTrainingHistory(useCache: false);
},
child: const Text("sync history?"),
)),
Expand Down Expand Up @@ -222,19 +218,12 @@ class TrainingHistoryCardManagementDialog extends StatelessWidget {
const TrainingHistoryCardManagementDialog(this.sesh, {super.key});
@override
Widget build(BuildContext context) {
final trainingHistoryCubit = context.read<TrainingHistoryCubit>();
final scaffoldMessenger = ScaffoldMessenger.of(context);
delSesh() async {
if (await cloudStorage.removeHistoryData(sesh)) {
trainingHistoryCubit.removeSession(sesh);
} else {
scaffoldMessenger.showSnackBar(
const SnackBar(
removeHistoryData(sesh).onError((error, stackTrace) => scaffoldMessenger.showSnackBar(const SnackBar(
content: Text('Failed to delete training session'),
duration: Duration(seconds: 2),
), //todo test me
);
}
)));
}

return AlertDialog(
Expand Down
20 changes: 0 additions & 20 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hydrated_bloc/hydrated_bloc.dart';
import 'package:open_fitness_tracker/DOM/training_metadata.dart';
import 'package:open_fitness_tracker/cloud_io/firestore_sync.dart';
import 'package:open_fitness_tracker/exercises/create_new_exercise/create_new_ex_modal.dart';
import 'package:open_fitness_tracker/exercises/ex_search_cubit.dart';
import 'package:open_fitness_tracker/firebase_options.dart';
Expand All @@ -25,10 +24,7 @@ Future<void> main() async {
HydratedStorage hydratedStorage = await HydratedStorage.build(
storageDirectory: kIsWeb ? HydratedStorage.webStorageDirectory : await getApplicationDocumentsDirectory(),
);

HydratedBloc.storage = hydratedStorage;
cloudStorage = FirestoreHydratedStorageSync(hydratedStorage);
cloudStorage.sync();

runApp(const MyApp());
}
Expand Down Expand Up @@ -62,22 +58,6 @@ class MyApp extends StatelessWidget {
context.read<TrainingSessionCubit>().updateDuration();
},
);

cloudStorage.setOnHistoryUpdate(() {
//just need to get the visual state update to trigger..
TrainingSession tempSesh = TrainingSession();
context.read<TrainingHistoryCubit>().addSession(tempSesh);
context.read<TrainingHistoryCubit>().removeSession(tempSesh);

//lets just update the traininghistorycubit here too & see what happens!
//storage.write(historyKey, {'trainingHistory': stringifiedHistory});
// var json = cloudStorage.storage.read('TrainingHistoryCubit');

// for (var str in json['trainingHistory']) {
// context.read<TrainingHistoryCubit>().addSession(TrainingSession.fromJson(str));
// }
});

return MaterialApp.router(
theme: myTheme,
routerConfig: routerConfig,
Expand Down

0 comments on commit 9a69183

Please sign in to comment.