Skip to content

Commit

Permalink
add observers for errors and analytics
Browse files Browse the repository at this point in the history
  • Loading branch information
mixin27 committed Aug 12, 2024
1 parent 8bfe991 commit 2e0fec5
Show file tree
Hide file tree
Showing 15 changed files with 340 additions and 61 deletions.
40 changes: 30 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
# mmcalendar
# Myanmar Calendar

A new Flutter project.
The Myanmar Calendar app is a beautifully designed tool that brings the traditional Myanmar calendar to your fingertips. Built with Flutter, this app offers a seamless experience across devices, ensuring you stay connected with Myanmar’s rich cultural heritage.

## Getting Started
## Libraries

This project is a starting point for a Flutter application.
- [flutter_mmcalendar](https://pub.dev/packages/flutter_mmcalendar)
- [table_calendar](https://pub.dev/packages/table_calendar)

A few resources to get you started if this is your first Flutter project:
## Project Setup

- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
To clone the repo for the first time

For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
```bash
git clone https://github.com/mixin27/mmcalendar.git
cd mmcalendar/
flutter packages get
```

Generate `build_runner` and `easy_localization`

```bash
# build_runner
dart run build_runner build

# easy_localization
dart run easy_localization:generate -S assets/translations -O lib/src/l10n -o locale_keys.g.dart -f keys
```

You will need to create firebase project to configure firebase

```bash
flutterfire configure
```

Go to onesignal, login or create an account and create an app. Then copy onesignalAppId and paste to `.env` file.
2 changes: 1 addition & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ android {
// Enables resource shrinking, which is performed by the
// Android Gradle plugin.
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
signingConfig signingConfigs.debug
Expand Down
36 changes: 0 additions & 36 deletions android/app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -1,36 +0,0 @@
#Flutter Wrapper
-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.** { *; }
-keep class io.flutter.util.** { *; }
-keep class io.flutter.view.** { *; }
-keep class io.flutter.** { *; }
-keep class io.flutter.plugins.** { *; }
-keep class io.flutter.plugin.editing.** { *; }

#Firebase
-keep class com.google.firebase.** { *; }
-keep class com.firebase.** { *; }
-keep class org.apache.** { *; }
-keepnames class com.fasterxml.jackson.** { *; }
-keepnames class javax.servlet.** { *; }
-keepnames class org.ietf.jgss.** { *; }
-dontwarn org.w3c.dom.**
-dontwarn org.joda.time.**
-dontwarn org.shaded.apache.**
-dontwarn org.ietf.jgss.**
-keepattributes Signature
-keepattributes *Annotation*
-keepattributes EnclosingMethod
-keepattributes InnerClasses
-keep class androidx.lifecycle.DefaultLifecycleObserver

#Crashlytics
-keepattributes SourceFile,LineNumberTable # Keep file names and line numbers.
-keep public class * extends java.lang.Exception
-keep class com.ito_technologies.soudan.** { *; }

#twilio_programmable_video
-keep class tvi.webrtc.** { *; }
-keep class com.twilio.video.** { *; }
-keep class com.twilio.common.** { *; }
-keepattributes InnerClasses
2 changes: 1 addition & 1 deletion android/settings.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ plugins {
id "com.google.firebase.firebase-perf" version "1.4.1" apply false
id "com.google.firebase.crashlytics" version "2.8.1" apply false
// END: FlutterFire Configuration
id "org.jetbrains.kotlin.android" version "1.7.10" apply false
id "org.jetbrains.kotlin.android" version "1.9.10" apply false
}

include ":app"
Binary file added assets/images/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
119 changes: 119 additions & 0 deletions lib/app_start_up.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mmcalendar/flutter_mmcalendar.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:mmcalendar/firebase_options.dart';
import 'package:mmcalendar/src/utils/onesignal/onesignal.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'app_start_up.g.dart';

@Riverpod(keepAlive: true)
FutureOr<void> appStartup(AppStartupRef ref) async {
ref.onDispose(() {
// ensure dependent providers are disposed as well
// ref.invalidate(onboardingRepositoryProvider);
});

MmCalendarConfig.initDefault(
const MmCalendarOptions(
language: Language.myanmar,
),
);

// await for all initialization code to be complete before returning
// we can use `Future.wait` for independent long run tasks.
await Future.wait([
// Firebase init
Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
),
initOnesignal(),

// list of providers to be warmed up
// ref.watch(onboardingRepositoryProvider.future),
// Future.delayed(const Duration(seconds: 5)),
]);
}

/// Widget class to manage asynchronous app initialization
class AppStartUpWidget extends ConsumerWidget {
const AppStartUpWidget({
super.key,
required this.onLoaded,
});

final WidgetBuilder onLoaded;

@override
Widget build(BuildContext context, WidgetRef ref) {
final appStartupState = ref.watch(appStartupProvider);

return appStartupState.when(
data: (_) => onLoaded(context),
loading: () => const AppStartupLoadingWidget(),
error: (e, st) => AppStartupErrorWidget(
message: e.toString(),
onRetry: () => ref.invalidate(appStartupProvider),
),
);
}
}

class AppStartupLoadingWidget extends StatelessWidget {
const AppStartupLoadingWidget({super.key});

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 100,
height: 100,
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/logo.png'),
),
),
),
const SizedBox(height: 20),
const CircularProgressIndicator.adaptive(),
],
),
),
),
);
}
}

class AppStartupErrorWidget extends StatelessWidget {
const AppStartupErrorWidget(
{super.key, required this.message, required this.onRetry});
final String message;
final VoidCallback onRetry;

@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(message, style: Theme.of(context).textTheme.headlineSmall),
const SizedBox(height: 16),
ElevatedButton(
onPressed: onRetry,
child: const Text('Retry'),
),
],
),
),
),
);
}
}
66 changes: 61 additions & 5 deletions lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,29 +1,44 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mmcalendar/flutter_mmcalendar.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:mmcalendar/firebase_options.dart';
import 'package:mmcalendar/src/features/app/app.dart';
import 'package:mmcalendar/src/shared/errors/async_error_logger.dart';
import 'package:mmcalendar/src/shared/errors/error_logger.dart';
import 'package:mmcalendar/src/utils/onesignal/onesignal.dart';

Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized();

MmCalendarConfig.initDefault(
const MmCalendarOptions(
language: Language.myanmar,
),
);

await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);

await initOnesignal();

MmCalendarConfig.initDefault(
const MmCalendarOptions(
language: Language.myanmar,
),
final container = ProviderContainer(
observers: [AsyncErrorLogger()],
);

// * Register error handlers. For more info, see:
// * https://docs.flutter.dev/testing/errors
final errorLogger = container.read(errorLoggerProvider);
registerErrorHandlers(errorLogger);

runApp(
ProviderScope(
UncontrolledProviderScope(
container: container,
child: EasyLocalization(
supportedLocales: const [
Locale('en'),
Expand All @@ -37,3 +52,44 @@ Future<void> main() async {
),
);
}

void registerErrorHandlers(ErrorLogger errorLogger) {
// * Show some error UI if any uncaught exception happens
FlutterError.onError = (FlutterErrorDetails details) {
FlutterError.presentError(details);
errorLogger.logError(details.exception, details.stack);

if (kReleaseMode) {
// Pass all uncaught "fatal" errors from the framework to Crashlytics
FirebaseCrashlytics.instance.recordFlutterFatalError(details);
}
};

// * Handle errors from the underlying platform/OS
PlatformDispatcher.instance.onError = (Object error, StackTrace stack) {
errorLogger.logError(error, stack);

// Pass all uncaught asynchronous errors that aren't handled by the Flutter framework to Crashlytics
if (kReleaseMode) {
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
}
return true;
};

// * Show some error UI when any widget in the app fails to build
ErrorWidget.builder = (FlutterErrorDetails details) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.redAccent,
title: const Text('An error occurred'),
),
body: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 20),
child: Text(details.toString()),
),
),
);
};
}
8 changes: 7 additions & 1 deletion lib/src/features/app/presentation/app.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:flutter/material.dart';
import 'package:mmcalendar/src/routes/routes.dart';

Expand All @@ -16,7 +17,12 @@ class AppWidget extends StatelessWidget {
colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF18363E)),
useMaterial3: true,
),
routerConfig: _appRouter.config(),
routerConfig: _appRouter.config(
navigatorObservers: () => [
AppRouteObserver(),
FirebaseAnalyticsObserver(analytics: FirebaseAnalytics.instance),
],
),

// localization stuffs
localizationsDelegates: context.localizationDelegates,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:iconly/iconly.dart';
import 'package:mmcalendar/src/routes/routes.dart';
Expand All @@ -22,14 +21,13 @@ class AppSettingsPage extends StatelessWidget {
const AppLanguageListTile(),
const RateMeListTile(),
const PrivacyPolicyListTile(),
AboutListTile(
icon: const Icon(IconlyLight.document),
const AboutListTile(
icon: Icon(IconlyLight.document),
applicationName: 'Myanmar Calendar',
applicationVersion: 'v1.0.0',
applicationIcon: const Icon(IconlyBroken.calendar),
applicationLegalese:
'Copyright (c) ${DateFormat().add_y().format(DateTime.now())} Kyaw Zayar Tun',
child: const Text('License'),
applicationIcon: Icon(IconlyBroken.calendar),
applicationLegalese: 'Copyright (c) 2024 Kyaw Zayar Tun',
child: Text('License'),
),
ListTile(
onTap: () => context.router.push(const AboutRoute()),
Expand Down
29 changes: 29 additions & 0 deletions lib/src/routes/app_route_observer.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import 'package:flutter/widgets.dart';

import 'package:auto_route/auto_route.dart';

class AppRouteObserver extends AutoRouteObserver {
@override
void didPush(Route route, Route? previousRoute) async {
debugPrint('New route pushed: ${route.settings.name}');
// await FirebaseAnalytics.instance.logEvent(
// name: 'screen_view',
// parameters: {
// 'screen_name': route.settings.name?.replaceAll('Route', 'Page') ?? '',
// 'screen_class': route.settings.name ?? '',
// },
// );
}

@override
void didPop(Route route, Route? previousRoute) {
debugPrint('Route popped: ${route.settings.name}');
}

@override
void didReplace({Route? newRoute, Route? oldRoute}) {
debugPrint(
'Route replaced: ${oldRoute?.settings.name} -> ${newRoute?.settings.name}',
);
}
}
1 change: 1 addition & 0 deletions lib/src/routes/routes.dart
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export 'app_router.dart';
export 'app_router.gr.dart';
export 'app_route_observer.dart';
Loading

0 comments on commit 2e0fec5

Please sign in to comment.