Skip to content

Commit

Permalink
Mobile: Prepare demo app POC with hardcoded data (#960)
Browse files Browse the repository at this point in the history
* Add demo survey and payment data sources
* Remove payments from user profile as not needed anymore
* Add user demo source
* Improve demo user source
* Add demo manager and demo button on start screen
* Add organization demo data source
* Add separate button text for create account page in demo mode
* Fix review changes
* Use RepositoryProvider to inject the DemoManager singleton into the widgets
* Just rename an unused variable
* Fix for "onError -- DashboardCardManagerCubit, Bad state: Cannot emit new states after calling close  -- 15729368"
This fixes the error "onError -- DashboardCardManagerCubit, Bad state: Cannot emit new states after calling close  -- 201270230" when you start the app and do the following:
- Click on the "Demo" button in the right upper corner => You are now on the Account page
- On Account page click on "Enter demo" button => You are now on the Dashboard
- Click on "Edit"  in the right upper corner => You are now on the profile page
- Scroll down to the "Sign out" button and click it => You are now on the Login page
- Now click again on the "Demo" button in the right upper corner => In the debug console you get "onError --DashboardCardManagerCubit, Bad state: Cannot emit new states after calling close  -- 15729368"

* Add code comment to explain the code area a bit more
* Use "MediaQuery.sizeOf(context)" instead of "MediaQuery.of(context).size" to query the size attribute because this results in better performance.
---------

Co-authored-by: Karin Berg <[email protected]>
  • Loading branch information
MDikkii and KarinBerg authored Jan 28, 2025
1 parent 36294a3 commit ecf5cf5
Show file tree
Hide file tree
Showing 30 changed files with 913 additions and 175 deletions.
16 changes: 10 additions & 6 deletions recipients_app/lib/core/cubits/auth/auth_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,14 @@ class AuthCubit extends Cubit<AuthState> {
if (user != null) {
try {
final recipient = await userRepository.fetchRecipient(user);
final Organization? organization = await _fetchOrganization(recipient);

emit(
AuthState(
status: AuthStatus.authenticated,
firebaseUser: user,
recipient: recipient,
organization: organization,
),
);
} on Exception catch (ex, stackTrace) {
Expand Down Expand Up @@ -56,12 +58,7 @@ class AuthCubit extends Cubit<AuthState> {

if (user != null) {
final recipient = await userRepository.fetchRecipient(user);
Organization? organization;

if (recipient?.organizationRef != null) {
organization = await organizationRepository
.fetchOrganization(recipient!.organizationRef!);
}
final Organization? organization = await _fetchOrganization(recipient);

emit(
AuthState(
Expand Down Expand Up @@ -104,4 +101,11 @@ class AuthCubit extends Cubit<AuthState> {
await userRepository.signOut();
emit(const AuthState());
}

Future<Organization?> _fetchOrganization(Recipient? recipient) async {
if (recipient?.organizationRef != null) {
return await organizationRepository.fetchOrganization(recipient!.organizationRef!);
}
return null;
}
}
4 changes: 3 additions & 1 deletion recipients_app/lib/core/cubits/auth/auth_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,21 @@ class AuthState extends Equatable {
});

@override
List<Object?> get props => [status, firebaseUser, recipient, exception];
List<Object?> get props => [status, firebaseUser, recipient, exception, organization];

AuthState copyWith({
AuthStatus? status,
User? firebaseUser,
Recipient? recipient,
Exception? exception,
Organization? organization,
}) {
return AuthState(
status: status ?? this.status,
firebaseUser: firebaseUser ?? this.firebaseUser,
recipient: recipient ?? this.recipient,
exception: exception ?? this.exception,
organization: organization ?? this.organization,
);
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import "dart:async";

import "package:app/core/cubits/auth/auth_cubit.dart";
import "package:app/data/repositories/repositories.dart";
import "package:app/view/widgets/account/dashboard_card.dart";
Expand All @@ -9,16 +11,23 @@ part "dashboard_card_manager_state.dart";
class DashboardCardManagerCubit extends Cubit<DashboardCardManagerState> {
final AuthCubit authCubit;
final CrashReportingRepository crashReportingRepository;
late StreamSubscription<AuthState> authSubscription;

DashboardCardManagerCubit({
required this.authCubit,
required this.crashReportingRepository,
}) : super(const DashboardCardManagerState()) {
authCubit.stream.listen((event) {
authSubscription = authCubit.stream.listen((event) {
fetchCards();
});
}

@override
Future<void> close() async {
authSubscription.cancel();
super.close();
}

Future<void> fetchCards() async {
emit(state.copyWith(status: DashboardCardManagerStatus.loading));
final recipient = authCubit.state.recipient;
Expand All @@ -29,7 +38,6 @@ class DashboardCardManagerCubit extends Cubit<DashboardCardManagerState> {

try {
// TODO: currently payment phone number is used for login, we need to switch that

final paymentPhoneNumber = recipient.mobileMoneyPhone;
final contactPhoneNumber = recipient.communicationMobilePhone;

Expand Down
149 changes: 149 additions & 0 deletions recipients_app/lib/data/datasource/demo/demo_user.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import "package:firebase_auth/firebase_auth.dart";

class DemoUser implements User {
@override
String? get displayName => "demo user";

@override
String? get email => null;

@override
bool get emailVerified => true;

@override
Future<String?> getIdToken([bool forceRefresh = false]) {
throw UnimplementedError();
}

@override
Future<void> delete() {
throw UnimplementedError();
}

@override
Future<IdTokenResult> getIdTokenResult([bool forceRefresh = false]) {
throw UnimplementedError();
}

@override
bool get isAnonymous => throw UnimplementedError();

@override
Future<UserCredential> linkWithCredential(AuthCredential credential) {
throw UnimplementedError();
}

@override
Future<ConfirmationResult> linkWithPhoneNumber(String phoneNumber, [RecaptchaVerifier? verifier]) {
throw UnimplementedError();
}

@override
Future<UserCredential> linkWithPopup(AuthProvider provider) {
throw UnimplementedError();
}

@override
Future<UserCredential> linkWithProvider(AuthProvider provider) {
throw UnimplementedError();
}

@override
Future<void> linkWithRedirect(AuthProvider provider) {
throw UnimplementedError();
}

@override
UserMetadata get metadata => throw UnimplementedError();

@override
MultiFactor get multiFactor => throw UnimplementedError();

@override
String? get phoneNumber => throw UnimplementedError();

@override
String? get photoURL => throw UnimplementedError();

@override
List<UserInfo> get providerData => throw UnimplementedError();

@override
Future<UserCredential> reauthenticateWithCredential(AuthCredential credential) {
throw UnimplementedError();
}

@override
Future<UserCredential> reauthenticateWithPopup(AuthProvider provider) {
throw UnimplementedError();
}

@override
Future<UserCredential> reauthenticateWithProvider(AuthProvider provider) {
throw UnimplementedError();
}

@override
Future<void> reauthenticateWithRedirect(AuthProvider provider) {
throw UnimplementedError();
}

@override
String? get refreshToken => throw UnimplementedError();

@override
Future<void> reload() {
throw UnimplementedError();
}

@override
Future<void> sendEmailVerification([ActionCodeSettings? actionCodeSettings]) {
throw UnimplementedError();
}

@override
String? get tenantId => throw UnimplementedError();

@override
String get uid => throw UnimplementedError();

@override
Future<User> unlink(String providerId) {
throw UnimplementedError();
}

@override
Future<void> updateDisplayName(String? displayName) {
throw UnimplementedError();
}

@override
Future<void> updateEmail(String newEmail) {
throw UnimplementedError();
}

@override
Future<void> updatePassword(String newPassword) {
throw UnimplementedError();
}

@override
Future<void> updatePhoneNumber(PhoneAuthCredential phoneCredential) {
throw UnimplementedError();
}

@override
Future<void> updatePhotoURL(String? photoURL) {
throw UnimplementedError();
}

@override
Future<void> updateProfile({String? displayName, String? photoURL}) {
throw UnimplementedError();
}

@override
Future<void> verifyBeforeUpdateEmail(String newEmail, [ActionCodeSettings? actionCodeSettings]) {
throw UnimplementedError();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import "package:cloud_firestore/cloud_firestore.dart";

// We are using DocumentReference in repository / data source. That's why we need to get
// no-op implementation for it for demo data source.

// ignore: subtype_of_sealed_class
class NoOpDocumentReference implements DocumentReference<Map<String, dynamic>> {
const NoOpDocumentReference();

@override
CollectionReference<Map<String, dynamic>> collection(String collectionPath) {
throw UnimplementedError();
}

@override
Future<void> delete() {
throw UnimplementedError();
}

@override
FirebaseFirestore get firestore => throw UnimplementedError();

@override
Future<DocumentSnapshot<Map<String, dynamic>>> get([GetOptions? options]) {
throw UnimplementedError();
}

@override
String get id => throw UnimplementedError();

@override
CollectionReference<Map<String, dynamic>> get parent => throw UnimplementedError();

@override
String get path => throw UnimplementedError();

@override
Future<void> set(Map<String, dynamic> data, [SetOptions? options]) {
throw UnimplementedError();
}

@override
Stream<DocumentSnapshot<Map<String, dynamic>>> snapshots(
{bool includeMetadataChanges = false, ListenSource source = ListenSource.defaultSource,}) {
throw UnimplementedError();
}

@override
Future<void> update(Map<Object, Object?> data) {
throw UnimplementedError();
}

@override
DocumentReference<R> withConverter<R>(
{required FromFirestore<R> fromFirestore, required ToFirestore<R> toFirestore,}) {
throw UnimplementedError();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import "package:app/data/datasource/organization_data_source.dart";
import "package:app/data/models/models.dart";
import "package:cloud_firestore/cloud_firestore.dart";

class OrganizationDemoDataSource implements OrganizationDataSource {
final Organization _organization = _generateOrganization();

static Organization _generateOrganization() {
return const Organization(name: "Demo organization", contactName: "Demo manager", contactNumber: "+232 123456789");
}

@override
Future<Organization?> fetchOrganization(DocumentReference<Object?> organizationRef) async {
return _organization;
}
}
Loading

0 comments on commit ecf5cf5

Please sign in to comment.