Flutter firebase service

  • Firebase Analytics
  • Firebase Crashlytics
  • Firebase auth: Google, Facebook, Apple
  • Firebase storage: select image + upload, gen URL download file
  • Cloud functions
  • Firebase messaging + local push notification

Integrate firebase

https://medium.com/@nhancv/flutter-create-a-firebase-project-183b681e4cb5

Integrate firebase Analytics

https://medium.com/@nhancv/flutter-analytics-integration-768ae76a8077

Integrate firebase Crashlytics

https://medium.com/@nhancv/flutter-crashlytics-integration-17530e24ba5c

pubspec.yaml

  # Firebase
  firebase_core: ^0.5.2+1
  firebase_analytics: ^6.2.0
  cloud_firestore: ^0.14.3+1
  firebase_storage: ^5.1.0
  image_picker: ^0.6.7+14
  cloud_functions: ^0.7.1
  firebase_crashlytics: ^0.2.3+1
  firebase_messaging: ^8.0.0-dev.9
  flutter_local_notifications: ^3.0.1+6
  firebase_auth: ^0.18.3+1
  google_sign_in: ^4.5.6
  apple_sign_in: ^0.1.0
  flutter_facebook_login: ^3.0.0

android/build.gradle

buildscript {
    ext.kotlin_version = '1.3.50'
    repositories {
        google()
        jcenter()
    }

    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.0'
        classpath 'com.google.gms:google-services:4.3.4'
        classpath 'com.google.firebase:firebase-crashlytics-gradle:2.3.0'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

rootProject.buildDir = '../build'
subprojects {
    project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
    project.evaluationDependsOn(':app')
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

android/app/build.gradle

...
dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'com.google.firebase:firebase-analytics:17.5.0'
    implementation 'com.google.firebase:firebase-crashlytics:17.2.2'
}

apply plugin: 'com.google.firebase.crashlytics'
apply plugin: 'com.google.gms.google-services'

android/app/src/main/res/values/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">AppName</string>

    <!-- Replace "000000000000" with your Facebook App ID here. -->
    <string name="facebook_app_id">000000000000</string>

    <!--
      Replace "000000000000" with your Facebook App ID here.
      **NOTE**: The scheme needs to start with `fb` and then your ID.
    -->
    <string name="fb_login_protocol_scheme">fb000000000000</string>
</resources>

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.app.nft">

    <uses-permission android:name="android.permission.INTERNET"/>

    <!-- io.flutter.app.FlutterApplication is an android.app.Application that
         calls FlutterMain.startInitialization(this); in its onCreate method.
         In most cases you can leave this as-is, but you if you want to provide
         additional functionality it is fine to subclass or reimplement
         FlutterApplication and put your custom class here. -->
    <application
        android:label="AppName"
        android:requestLegacyExternalStorage="true"
        android:usesCleartextTraffic="true"
        android:icon="@mipmap/ic_launcher">
        <activity
            android:name=".MainActivity"
            android:launchMode="singleTop"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize"
            android:showWhenLocked="true"
            android:turnScreenOn="true">
            <!-- Specifies an Android theme to apply to this Activity as soon as
                 the Android process has started. This theme is visible to the user
                 while the Flutter UI initializes. After that, this theme continues
                 to determine the Window background behind the Flutter UI. -->
            <meta-data
              android:name="io.flutter.embedding.android.NormalTheme"
              android:resource="@style/NormalTheme"
              />
            <!-- Displays an Android View that continues showing the launch screen
                 Drawable until Flutter paints its first frame, then this splash
                 screen fades out. A splash screen is useful to avoid any visual
                 gap between the end of Android's launch screen and the painting of
                 Flutter's first frame. -->
            <meta-data
              android:name="io.flutter.embedding.android.SplashScreenDrawable"
              android:resource="@drawable/launch_background"
              />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <!-- Don't delete the meta-data below.
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />

        <!-- A custom Android Notification Channel to deliver FCM notifications on a non-default channel -->
        <meta-data
                android:name="com.google.firebase.messaging.default_notification_channel_id"
                android:value="high_importance_channel" />

        <!-- Facebook config https://pub.dev/packages/flutter_facebook_login -->
        <meta-data android:name="com.facebook.sdk.ApplicationId"
                   android:value="@string/facebook_app_id"/>

        <activity android:name="com.facebook.FacebookActivity"
                  android:configChanges=
                          "keyboard|keyboardHidden|screenLayout|screenSize|orientation"
                  android:label="@string/app_name" />

        <activity
                android:name="com.facebook.CustomTabActivity"
                android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />
                <data android:scheme="@string/fb_login_protocol_scheme" />
            </intent-filter>
        </activity>

    </application>
</manifest>

android/app/proguard-rules.pro

#https://github.com/MaikuB/flutter_local_notifications/blob/master/flutter_local_notifications/example/android/app/proguard-rules.pro
## 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.**  { *; }
-dontwarn io.flutter.embedding.**

## Gson rules
# Gson uses generic type information stored in a class file when working with fields. Proguard
# removes such information by default, so configure it to keep all of it.
-keepattributes Signature

# For using GSON @Expose annotation
-keepattributes *Annotation*

# Gson specific classes
-dontwarn sun.misc.**
#-keep class com.google.gson.stream.** { *; }

# Prevent proguard from stripping interface information from TypeAdapter, TypeAdapterFactory,
# JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)
-keep class * extends com.google.gson.TypeAdapter
-keep class * implements com.google.gson.TypeAdapterFactory
-keep class * implements com.google.gson.JsonSerializer
-keep class * implements com.google.gson.JsonDeserializer

# Prevent R8 from leaving Data object members always null
-keepclassmembers,allowobfuscation class * {
  @com.google.gson.annotations.SerializedName <fields>;
}

ios/Runner/Runner.entitlements

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>aps-environment</key>
	<string>development</string>
	<key>com.apple.developer.applesignin</key>
	<array>
		<string>Default</string>
	</array>
</dict>
</plist>

Info.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>$(DEVELOPMENT_LANGUAGE)</string>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>AppName</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>$(FLUTTER_BUILD_NAME)</string>
	<key>CFBundleSignature</key>
	<string>????</string>
	<key>CFBundleVersion</key>
	<string>$(FLUTTER_BUILD_NUMBER)</string>
	<key>ITSAppUsesNonExemptEncryption</key>
	<false/>
	<key>LSRequiresIPhoneOS</key>
	<true/>
	<key>NSAppTransportSecurity</key>
	<dict>
		<key>NSAllowsLocalNetworking</key>
		<true/>
	</dict>
	<key>NSCameraUsageDescription</key>
	<string>App requires access to the camera for avatar upload</string>
	<key>NSPhotoLibraryUsageDescription</key>
	<string>App requires access to the photo library for avatar upload</string>
	<key>UIBackgroundModes</key>
	<array>
		<string>fetch</string>
		<string>remote-notification</string>
	</array>
	<key>UILaunchStoryboardName</key>
	<string>LaunchScreen</string>
	<key>UIMainStoryboardFile</key>
	<string>Main</string>
	<key>UISupportedInterfaceOrientations</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
	</array>
	<key>UISupportedInterfaceOrientations~ipad</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationPortraitUpsideDown</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
	<key>UIViewControllerBasedStatusBarAppearance</key>
	<false/>
	<!-- Put me in the [my_project]/ios/Runner/Info.plist file -->
    <!-- Google Sign-in Section -->
	<key>CFBundleURLTypes</key>
    <array>
        <dict>
            <key>CFBundleTypeRole</key>
            <string>Editor</string>
            <key>CFBundleURLSchemes</key>
            <array>
                <!-- TODO Replace this value: -->
            	<!-- Copied from GoogleService-Info.plist key REVERSED_CLIENT_ID -->
                <string>com.googleusercontent.apps.1022986619993-tgkhigi1onm47tbp2iic1cnbuun9l788</string>
            </array>
        </dict>
        <dict>
            <key>CFBundleURLSchemes</key>
            <array>
                <!--
                  Replace "000000000000" with your Facebook App ID here.
                  **NOTE**: The scheme needs to start with `fb` and then your ID.
                -->
                <string>fb000000000000</string>
            </array>
        </dict>
    </array>

    <key>FacebookAppID</key>

    <!-- Replace "000000000000" with your Facebook App ID here. -->
    <string>000000000000</string>
    <key>FacebookDisplayName</key>

    <!-- Replace "YOUR_APP_NAME" with your Facebook App name. -->
    <string>YOUR_APP_NAME</string>

    <key>LSApplicationQueriesSchemes</key>
    <array>
        <string>fbapi</string>
        <string>fb-messenger-share-api</string>
        <string>fbauth2</string>
        <string>fbshareextension</string>
    </array>
    <!-- End of the Google and Fb Sign-in Section -->
</dict>
</plist>

AppDelegate.swift

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    if #available(iOS 10.0, *) {
      UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
    }
    if(!UserDefaults.standard.bool(forKey: "Notification")) {
        UIApplication.shared.cancelAllLocalNotifications()
        UserDefaults.standard.set(true, forKey: "Notification")
    }
    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
}

my_app.dart

import 'dart:async';
import 'dart:isolate';

import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:firebase_analytics/observer.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:nft/generated/l10n.dart';
import 'package:nft/pages/home/home_provider.dart';
import 'package:nft/pages/login/login_provider.dart';
import 'package:nft/pages/play/play_provider.dart';
import 'package:nft/services/app/app_dialog.dart';
import 'package:nft/services/app/app_loading.dart';
import 'package:nft/services/cache/credential.dart';
import 'package:nft/services/cache/storage.dart';
import 'package:nft/services/cache/storage_preferences.dart';
import 'package:nft/services/app/locale_provider.dart';
import 'package:nft/services/firebase/firebase_push.dart';
import 'package:nft/services/firebase/firebase_tracking.dart';
import 'package:nft/services/firebase/local_push.dart';
import 'package:nft/services/rest_api/api_user.dart';
import 'package:nft/utils/app_constant.dart';
import 'package:nft/utils/app_route.dart';
import 'package:nft/utils/app_theme.dart';
import 'package:provider/provider.dart';
import 'package:provider/single_child_widget.dart';

Future<void> myMain() async {
  /// Start services later
  WidgetsFlutterBinding.ensureInitialized();

  /// Force portrait mode
  await SystemChrome.setPreferredOrientations(
      <DeviceOrientation>[DeviceOrientation.portraitUp]);

  // Initializing FlutterFire
  await Firebase.initializeApp();

  // Initialize Crash report
  const bool enableCrashlytics = !kDebugMode;
  await FirebaseCrashlytics.instance
      .setCrashlyticsCollectionEnabled(enableCrashlytics);

  // Pass all uncaught errors from the framework to Crashlytics.
  FlutterError.onError = FirebaseCrashlytics.instance.recordFlutterError;

  // Errors outside of Flutter
  Isolate.current.addErrorListener(RawReceivePort((List<dynamic> pair) async {
    final List<dynamic> errorAndStacktrace = pair;
    await FirebaseCrashlytics.instance.recordError(
      errorAndStacktrace.first,
      errorAndStacktrace.last as StackTrace,
    );
  }).sendPort);

  // Config firebase message
  await FirebasePush.I.configBeforeRunApp();

  /// Run Application
  // Zoned Errors
  runZonedGuarded<Future<void>>(() async {
    /// Run Application
    runApp(
      MultiProvider(
        providers: <SingleChildWidget>[
          Provider<AppRoute>(create: (_) => AppRoute()),
          Provider<Storage>(create: (_) => StoragePreferences()),
          ChangeNotifierProvider<Credential>(
              create: (BuildContext context) =>
                  Credential(context.read<Storage>())),
          ProxyProvider<Credential, ApiUser>(
              create: (_) => ApiUser(),
              update: (_, Credential credential, ApiUser userApi) {
                return userApi..token = credential.token;
              }),
          Provider<AppLoadingProvider>(create: (_) => AppLoadingProvider()),
          Provider<AppDialogProvider>(create: (_) => AppDialogProvider()),
          ChangeNotifierProvider<LocaleProvider>(
              create: (_) => LocaleProvider()),
          ChangeNotifierProvider<AppThemeProvider>(
              create: (_) => AppThemeProvider()),
          ChangeNotifierProvider<HomeProvider>(
              create: (BuildContext context) => HomeProvider(
                    context.read<ApiUser>(),
                    context.read<Credential>(),
                  )),
          ChangeNotifierProvider<PlayProvider>(
              create: (BuildContext context) => PlayProvider()),
          ChangeNotifierProvider<LoginProvider>(
              create: (BuildContext context) => LoginProvider(
                    context.read<ApiUser>(),
                    context.read<Credential>(),
                  )),
        ],
        child: const MyApp(),
      ),
    );
  }, FirebaseCrashlytics.instance.recordError);
}

class MyApp extends StatefulWidget {
  const MyApp({Key key}) : super(key: key);

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  static FirebaseAnalyticsObserver observer =
      FirebaseAnalyticsObserver(analytics: FirebaseTracking.I.analytics);

  @override
  void initState() {
    super.initState();

    // Init firebase push
    FirebasePush.I.configInAppState();
    // Init local push
    LocalPush.I.configInAppState(
      onSelectNotification: (String payload) async {
        print('onSelectNotification $payload');
      },
      onDidReceiveLocalNotification:
          (int id, String title, String body, String payload) async {
        print('onDidReceiveLocalNotification $payload');
      },
    );

    // Example about load credential to init page
    WidgetsBinding.instance.addPostFrameCallback((_) async {
      final bool hasCredential =
          await context.read<Credential>().loadCredential();
      if (hasCredential) {
        context.navigator()?.pushReplacementNamed(AppConstant.homePageRoute);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    // Get providers
    final AppRoute appRoute = context.watch<AppRoute>();
    final LocaleProvider localeProvider = context.watch<LocaleProvider>();
    final AppTheme appTheme = context.theme();
    // Build Material app
    return MaterialApp(
      navigatorKey: appRoute.navigatorKey,
      locale: localeProvider.locale,
      supportedLocales: S.delegate.supportedLocales,
      localizationsDelegates: const <LocalizationsDelegate<dynamic>>[
        S.delegate,
        GlobalMaterialLocalizations.delegate,
        GlobalCupertinoLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
      ],
      debugShowCheckedModeBanner: false,
      theme: appTheme.buildThemeData(),
      //https://stackoverflow.com/questions/57245175/flutter-dynamic-initial-route
      //https://github.com/flutter/flutter/issues/12454
      //home: (appRoute.generateRoute(
      ///            const RouteSettings(name: AppConstant.rootPageRoute))
      ///        as MaterialPageRoute<dynamic>)
      ///    .builder(context),
      initialRoute: AppConstant.demoPageRoute,
      // initialRoute: AppConstant.rootPageRoute,
      onGenerateRoute: appRoute.generateRoute,
      navigatorObservers: <NavigatorObserver>[appRoute.routeObserver, observer],
    );
  }

}

demo_page.dart

import 'dart:async';
import 'dart:io';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:habitapp/services/app/app_loading.dart';
import 'package:habitapp/services/app/dynamic_size.dart';
import 'package:habitapp/services/firebase/cloud_firestore.dart';
import 'package:habitapp/services/firebase/cloud_functions.dart';
import 'package:habitapp/services/firebase/cloud_storage.dart';
import 'package:habitapp/services/firebase/firebase_error.dart';
import 'package:habitapp/services/firebase/firebase_error_type.dart';
import 'package:habitapp/services/firebase/firebase_login.dart';
import 'package:habitapp/services/firebase/local_push.dart';
import 'package:habitapp/utils/app_helper.dart';
import 'package:habitapp/utils/app_log.dart';
import 'package:habitapp/utils/app_style.dart';
import 'package:habitapp/widgets/p_appbar_empty.dart';
import 'package:habitapp/widgets/w_dismiss_keyboard.dart';
import 'package:image_picker/image_picker.dart';
import 'package:provider/provider.dart';

class DemoPage extends StatefulWidget {
  @override
  _DemoPageState createState() => _DemoPageState();
}

class _DemoPageState extends State<DemoPage>
    with WidgetsBindingObserver, DynamicSize, FirebaseError {
  CloudFireStoreSnapshot _cloudFireStoreSnapshot;
  StreamSubscription<DocumentSnapshot> snapshotSubscription;

  @override
  void initState() {
    super.initState();
    print('page1_initState');
    WidgetsBinding.instance.addObserver(this);
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (_cloudFireStoreSnapshot != null) {
        snapshotSubscription =
            CloudFireStore.I.listenUser(_cloudFireStoreSnapshot);
      }
    });
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print('page1_didChangeDependencies');
  }

  @override
  void didUpdateWidget(covariant DemoPage oldWidget) {
    super.didUpdateWidget(oldWidget);
    print('page1_didUpdateWidget');
  }

  @override
  void deactivate() {
    print('page1_deactivate');
    super.deactivate();
  }

  @override
  void dispose() {
    print('page1_dispose');
    snapshotSubscription?.cancel();
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);
    print('page1_didChangeAppLifecycleState: $state');
  }

  @override
  Widget build(BuildContext context) {
    initDynamicSize(context);
    return PAppBarEmpty(
      child: WDismissKeyboard(
        child: Container(
            width: double.infinity,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              mainAxisSize: MainAxisSize.max,
              children: <Widget>[
                FlatButton(
                  onPressed: () {
                    FirebaseLogin.I.signInGoogle();
                  },
                  child: const Text(
                    'Google Sign in',
                  ),
                ),
                FlatButton(
                  onPressed: () {
                    FirebaseLogin.I.signInFacebook();
                  },
                  child: const Text(
                    'Facebook Sign in',
                  ),
                ),
                FutureBuilder<bool>(
                  future: FirebaseLogin.I.isAppleSignInAvailable(),
                  builder: (_, AsyncSnapshot<bool> isAvailable) {
                    return isAvailable.data == true
                        ? FlatButton(
                            onPressed: () {
                              FirebaseLogin.I.signInApple();
                            },
                            child: const Text(
                              'Facebook Apple',
                            ),
                          )
                        : Container();
                  },
                ),
                FlatButton(
                  onPressed: () {
                    LocalPush.I.schedule(
                        id: 0,
                        message: 'local push',
                        notifyAt:
                            DateTime.now().add(const Duration(seconds: 3)),
                        payload: NotyPayload(0, data: 'default'));
                  },
                  child: const Text(
                    'Local push',
                  ),
                ),
                FlatButton(
                  onPressed: () {
                    firebaseCallSafety(
                      CloudFunctions.I.getHelloWorld,
                      onStart: () async {
                        print('Start call function');
                      },
                      onCompleted: (bool status, void _) async {
                        print('Call function status: $status');
                      },
                      onError: (dynamic error) async {
                        print(error);
                      },
                    );
                  },
                  child: const Text(
                    'Hello world',
                  ),
                ),
                FlatButton(
                  onPressed: () {
                    // Forcing a crash
                    FirebaseCrashlytics.instance.crash();
                  },
                  child: const Text('Crash'),
                ),
                FlatButton(
                  onPressed: () {
                    firebaseCallSafety(CloudFireStore.I.addUserWithId,
                        onCompleted: (bool status, void _) async {
                      print('User added with status: $status');
                    });
                  },
                  child: const Text(
                    'Add User',
                  ),
                ),
                FlatButton(
                  onPressed: () {
                    firebaseCallSafety(CloudFireStore.I.updateUser,
                        onCompleted: (bool status, void _) async {
                      print('User updated with status: $status');
                    });
                  },
                  child: const Text(
                    'Update User',
                  ),
                ),
                FlatButton(
                  onPressed: () {
                    firebaseCallSafety(CloudFireStore.I.removeExtraField,
                        onCompleted: (bool status, void _) async {
                      print('User remove extra field with status: $status');
                    });
                  },
                  child: const Text(
                    'Remove extra field',
                  ),
                ),
                FlatButton(
                  onPressed: () {
                    firebaseCallSafety(CloudFireStore.I.deleteUser,
                        onCompleted: (bool status, void _) async {
                      print('User deleted with status: $status');
                    });
                  },
                  child: const Text(
                    'Delete user',
                  ),
                ),
                FlatButton(
                  onPressed: () {
                    firebaseCallSafety(() => CloudFireStore.I.readUser(),
                        onCompleted: (bool status, String value) async {
                      print('User read with status: $status, value: $value');
                      if (status == true) {
                        AppHelper.showToast(value);
                      }
                    });
                  },
                  child: const Text(
                    'Read user info',
                  ),
                ),
                ChangeNotifierProvider<CloudFireStoreSnapshot>(
                  create: (_) => CloudFireStoreSnapshot(),
                  builder: (BuildContext context, _) {
                    _cloudFireStoreSnapshot =
                        Provider.of<CloudFireStoreSnapshot>(context,
                            listen: false);
                    return Selector<CloudFireStoreSnapshot, DocumentSnapshot>(
                      selector: (_, CloudFireStoreSnapshot p) => p.userSnapshot,
                      builder: (_, DocumentSnapshot snapshot, __) {
                        print('update changes page1');
                        return Text(snapshot?.exists == true
                            ? snapshot.data()['full_name'] as String
                            : 'null');
                      },
                    );
                  },
                ),
                IconButton(
                  icon: const Icon(Icons.photo_library),
                  onPressed: () {
                    _selectImageSource(context);
                  },
                ),
              ],
            )),
      ),
    );
  }

  Future<void> _selectImageSource(BuildContext context) async {
    // Close keyboard
    FocusScope.of(context).requestFocus(FocusNode());

    // Define max size of image
    const double maxWidth = 350;
    const double maxHeight = 500;

    // Show choose image source
    final List<String> source = <String>['Camera', 'Gallery'];
    final List<CupertinoActionSheetAction> actions = source
        .map((String it) => CupertinoActionSheetAction(
              isDefaultAction: true,
              onPressed: () {
                // pop value
                Navigator.pop<int>(context, source.indexOf(it));
              },
              child: Text(
                it,
                style: boldTextStyle(14, Colors.black),
              ),
            ))
        .toList(growable: false);

    final int index = await showCupertinoModalPopup<int>(
      context: context,
      builder: (BuildContext context) => CupertinoActionSheet(
        actions: actions,
        cancelButton: CupertinoActionSheetAction(
          isDefaultAction: true,
          isDestructiveAction: true,
          onPressed: () {
            // pop null
            Navigator.pop(context);
          },
          child: Text(
            'Close',
            style: boldTextStyle(14, Colors.red),
          ),
        ),
      ),
    );

    if (index != null) {
      final ImagePicker _imagePicker = ImagePicker();
      final PickedFile image = await _imagePicker.getImage(
        source: index == 0 ? ImageSource.camera : ImageSource.gallery,
        maxWidth: maxWidth,
        maxHeight: maxHeight,
      );
      if (image != null) {
        final File file = File(image.path);

        // Upload media image
        firebaseCallSafety(() {
          return CloudStorage.I.uploadFile(file);
        }, onStart: () async {
          AppLoadingProvider.show(context);
        }, onCompleted: (bool status, void _) async {
          AppLoadingProvider.hide(context);
          if (status) {
            AppHelper.showToast('success');
          }
        });
      }
    }
  }

  @override
  Future<void> onFirebaseError(dynamic error) async {
    final FirebaseErrorType firebaseErrorType = parseFirebaseErrorType(error);
    print(firebaseErrorType);
    AppHelper.showToast(firebaseErrorType.message);
  }
}

firebase_tracking.dart

import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:flutter/foundation.dart';

class FirebaseTracking {
  FirebaseTracking._() {
    disableLog = !kReleaseMode;
  }

  static FirebaseTracking I = FirebaseTracking._();

  // Firebase analytics instance
  final FirebaseAnalytics _analytics = FirebaseAnalytics();

  FirebaseAnalytics get analytics => _analytics;

  // When the debug slider is ON and no event data is sent
  bool _disableLog = true;

  set disableLog(bool value) {
    _disableLog = value;
    _analytics.setAnalyticsCollectionEnabled(!value);
  }

  /// Log button tapped event
  void logEvent({String name, Map<String, dynamic> parameters}) {
    if (_disableLog) {
      return;
    }
    if (name == null || name.isEmpty) {
      return;
    }
    _analytics.logEvent(name: name, parameters: parameters);
  }

  /// Tracking screen
  void trackingScreen(String screenName) {
    if (_disableLog) {
      return;
    }
    _analytics.setCurrentScreen(screenName: screenName);
  }
}

firebase_error.dart

import 'dart:async';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:nft/services/firebase/firebase_error_type.dart';
import 'package:nft/utils/app_log.dart';

mixin FirebaseError {
  /// This function was called when trigger safeCallApi
  /// and apiError = true as default
  Future<void> onFirebaseError(dynamic error);

  /// Call api safety with error handling.
  /// Required:
  /// - dioApi: call async dio function
  /// Optional:
  /// - onStart: the function executed before api, can be null
  /// - onError: the function executed in case api crashed, can be null
  /// - onCompleted: the function executed after api or before crashing, can be null
  /// - onFinally: the function executed end of function, can be null
  /// - apiError: true as default if you want to forward the error to onApiError
  Future<T> firebaseCallSafety<T>(
    Future<T> Function() fireCall, {
    Future<void> Function() onStart,
    Future<void> Function(dynamic error) onError,
    Future<void> Function(bool status, T res) onCompleted,
    Future<void> Function() onFinally,
    bool skipOnError = true,
  }) async {
    try {
      // On start, use for show loading
      if (onStart != null) {
        await onStart();
      }

      // Execute api
      final T res = await fireCall();

      // On completed, use for hide loading
      if (onCompleted != null) {
        await onCompleted(true, res);
      }
      // Return api response
      return res;
    } catch (error) {
      // In case error:
      // On completed, use for hide loading
      if (onCompleted != null) {
        await onCompleted(false, null);
      }

      // On inline error
      if (onError != null) {
        await onError(error);
      }

      // Call onApiError if apiError's enabled
      if (skipOnError) {
        onFirebaseError(error);
      }

      return null;
    } finally {
      // Call finally function
      if (onFinally != null) {
        await onFinally();
      }
    }
  }

  // Parsing error to ErrorType
  FirebaseErrorType parseFirebaseErrorType(dynamic error) {
    if (error is FirebaseException) {
      FirebaseErrorCode errorCode = FirebaseErrorCode.unknown;
      if(error.code == 'permission-denied') {
        errorCode = FirebaseErrorCode.permission;
      }
      return FirebaseErrorType(code: errorCode, message: error.message);
    } else {
      logger.e(error);
    }
    return FirebaseErrorType();
  }
}

firebase_error_type.dart

enum FirebaseErrorCode { unknown, permission }

class FirebaseErrorType {
  FirebaseErrorType({this.code = FirebaseErrorCode.unknown, this.message = 'Unknown'});

  final FirebaseErrorCode code;
  final String message;

  @override
  String toString() {
    return 'FirebaseErrorType{code: $code, message: $message}';
  }
}

cloud_firestore.dart

import 'dart:async';

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:nft/services/safety/change_notifier_safety.dart';

const String defaultDocumentId = 'g5P2RamQ0z00XeL08Kk2';

// dev rule
//rules_version = '2';
// service cloud.firestore {
//   match /databases/{database}/documents {
//     match /{document=**} {
//       allow read, write: if
//           request.time < timestamp.date(2020, 12, 27);
//     }
//   }
// }
class CloudFireStore {
  CloudFireStore._();

  static final CloudFireStore I = CloudFireStore._();

  final FirebaseFirestore _store = FirebaseFirestore.instance;

  // Insert
  Future<void> addUser() {
    final CollectionReference users = _store.collection('users');
    final Map<String, String> data = <String, String>{
      'full_name': 'test',
      'extra': 'empty'
    };
    return users.add(data);
  }

  // Insert with defined id
  Future<void> addUserWithId({String documentId = defaultDocumentId}) {
    final CollectionReference users = _store.collection('users');
    final Map<String, String> data = <String, String>{
      'full_name': 'test',
      'extra': 'empty'
    };
    return users.doc(documentId).set(data);
  }

  // Update document
  Future<void> updateUser({String documentId = defaultDocumentId}) {
    final CollectionReference users = _store.collection('users');
    final Map<String, String> data = <String, String>{
      'full_name': DateTime.now().toIso8601String()
    };
    return users.doc(documentId).update(data);
  }

  // Remove specific properties
  Future<void> removeExtraField({String documentId = defaultDocumentId}) {
    final CollectionReference users = _store.collection('users');
    return users
        .doc(documentId)
        .update(<String, FieldValue>{'extra': FieldValue.delete()});
  }

  // Delete document
  Future<void> deleteUser({String documentId = defaultDocumentId}) {
    final CollectionReference users = _store.collection('users');
    return users.doc(documentId).delete();
  }

  // One-time Read
  Future<String> readUser({String documentId = defaultDocumentId}) async {
    final CollectionReference users = _store.collection('users');
    final DocumentSnapshot snapshot = await users.doc(documentId).get();
    if (!snapshot.exists) {
      throw Exception('User does not exist!');
    }
    return snapshot.data()['full_name'] as String;
  }

  // Real-time Read
  // Remember cancel stream in dispose widget
  StreamSubscription<DocumentSnapshot> listenUser(
      final CloudFireStoreSnapshot cloudFireStoreSnapshot,
      {String documentId = defaultDocumentId}) {
    print('cloudFireStoreSnapshot ${cloudFireStoreSnapshot.hashCode}');
    final CollectionReference users = _store.collection('users');
    final Stream<DocumentSnapshot> documentStream =
        users.doc(documentId).snapshots();
    final StreamSubscription<DocumentSnapshot> subscription =
        documentStream.listen((DocumentSnapshot event) {
      print('update_cloudFireStoreSnapshot ${cloudFireStoreSnapshot.hashCode}');
      cloudFireStoreSnapshot?.userSnapshot = event;
    });
    return subscription;
  }
}

class CloudFireStoreSnapshot extends ChangeNotifierSafety {
  DocumentSnapshot _userSnapshot;

  DocumentSnapshot get userSnapshot => _userSnapshot;

  set userSnapshot(DocumentSnapshot value) {
    _userSnapshot = value;
    notifyListeners();
  }
}

cloud_storage.dart

import 'dart:async';
import 'dart:io';
import 'package:nft/utils/app_log.dart';
import 'package:path/path.dart';
import 'package:firebase_storage/firebase_storage.dart';

// dev rule
//rules_version = '2';
// service firebase.storage {
//   match /b/{bucket}/o {
//     match /{allPaths=**} {
//       allow read, write: if
//           request.time < timestamp.date(2020, 12, 27);
//     }
//   }
// }
class CloudStorage {
  CloudStorage._();

  static final CloudStorage I = CloudStorage._();

  final FirebaseStorage _storage = FirebaseStorage.instance;

  // Parse file name
  String _parseFilename(File file) {
    final String extension = basename(file.path).split('.').last;
    final int nowTimeStamp = DateTime.now().toUtc().millisecondsSinceEpoch;
    return '$nowTimeStamp.$extension';
  }

  // List files
  Future<void> listFiles() async {
    final ListResult result = await FirebaseStorage.instance.ref().listAll();

    // ignore: avoid_function_literals_in_foreach_calls
    result.items.forEach((Reference ref) {
      print('Found file: $ref');
    });

    // ignore: avoid_function_literals_in_foreach_calls
    result.prefixes.forEach((Reference ref) {
      print('Found directory: $ref');
    });
  }

  // Upload file
  Future<void> uploadFile(File file) async {
    final String uploadFileName = _parseFilename(file);
    final UploadTask uploadTask =
        FirebaseStorage.instance.ref('uploads/$uploadFileName').putFile(file);
    final StreamSubscription<TaskSnapshot> streamSubscription =
        uploadTask.snapshotEvents.listen((TaskSnapshot snapshot) {
      // paused, running, success
      logger.d('Snapshot state: ${snapshot.state}');
      logger.d('Progress: ${snapshot.totalBytes / snapshot.bytesTransferred}');
    });
    await uploadTask;
    streamSubscription.cancel();
  }

  // Generate download file url
  Future<String> getDownloadURL(String path) =>
      _storage.ref(path).getDownloadURL().then((String url) => url);
}

cloud_functions.dart

import 'dart:async';
import 'dart:io';
import 'package:nft/utils/app_log.dart';
import 'package:cloud_functions/cloud_functions.dart';

// Test local with mobile emulator and firebase emulators
const bool EMULATOR = true;

class CloudFunctions {
  CloudFunctions._();

  static final CloudFunctions I = CloudFunctions._();

  final FirebaseFunctions _functions = FirebaseFunctions.instance;

  // Call default cloud function
  Future<void> getHelloWorld() async {
    logger.d('Call cloud function helloWorld');
    if (EMULATOR) {
      _functions.useFunctionsEmulator(origin: 'http://localhost:5001');
    }
    // Format return from server:
    // response.send({data: "Hello from Firebase!", error: ""});
    final HttpsCallable callable = _functions.httpsCallable('helloWorld');
    final HttpsCallableResult<String> results = await callable();
    final String hello = results.data;
    logger.d('From server: $hello');
  }
}

firebase_push.dart

import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';

/// Define a top-level named handler which background/terminated messages will
/// call.
///
/// To verify things are working, check out the native platform logs.
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  // If you're going to use other Firebase services in the background, such as Firestore,
  // make sure you call `initializeApp` before using other Firebase services.
  await Firebase.initializeApp();
  print('Handling a background message ${message.messageId}');
}

/// Create a [AndroidNotificationChannel] for heads up notifications
const AndroidNotificationChannel _channel = AndroidNotificationChannel(
  'high_importance_channel', // id
  'High Importance Notifications', // title
  'This channel is used for important notifications.', // description
  importance: Importance.high,
  enableVibration: true,
  playSound: true,
);

/// Initialize the [FlutterLocalNotificationsPlugin] package.
final FlutterLocalNotificationsPlugin _flutterLocalNotificationsPlugin =
    FlutterLocalNotificationsPlugin();

class FirebasePush {
  FirebasePush._();

  static FirebasePush I = FirebasePush._();

  // Firebase messaging instance
  final FirebaseMessaging _messaging = FirebaseMessaging.instance;

  // Config place before runApp()
  Future<void> configBeforeRunApp() async {
    // Set the background messaging handler early on, as a named top-level function
    FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);

    /// Config: https://firebase.flutter.dev/docs/messaging/notifications
    /// iOS: Enabling foreground notifications is generally a straightforward process.
    /// Update the iOS foreground notification presentation options to allow
    /// heads up notifications.
    await _messaging.setForegroundNotificationPresentationOptions(
      alert: true, // Required to display a heads up notification
      badge: true,
      sound: true,
    );

    /// We use this channel in the `AndroidManifest.xml` file to override the
    /// default FCM channel to enable heads up notifications.
    await _flutterLocalNotificationsPlugin
        .resolvePlatformSpecificImplementation<
            AndroidFlutterLocalNotificationsPlugin>()
        ?.createNotificationChannel(_channel);
  }

  // Place this config in initState of Application Widget
  Future<void> configInAppState() async {
    // Request permission
    final NotificationSettings settings = await _messaging.requestPermission(
      alert: true,
      announcement: false,
      badge: true,
      carPlay: false,
      criticalAlert: false,
      provisional: false,
      sound: true,
    );
    if (settings.authorizationStatus == AuthorizationStatus.authorized) {
      print('User granted permission');

      ////////////////////////////////////////////////////////////////////
      // Get any messages which caused the application to open from
      // a terminated state.
      final RemoteMessage initialMessage =
          await FirebaseMessaging.instance.getInitialMessage();
      print('initialMessage: ${initialMessage?.notification?.title}');

      // Also handle any interaction when the app is in the background via a
      // Stream listener
      FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
        print('A new onMessageOpenedApp event was published!');
        print('onMessageOpenedApp: ${message.notification.title}');
      });

      // Listen new message
      FirebaseMessaging.onMessage.listen((RemoteMessage message) {
        print('message: ${message.notification.title}');
        final RemoteNotification notification = message.notification;
        final AndroidNotification android = message.notification?.android;

        if (notification != null && android != null) {
          _flutterLocalNotificationsPlugin.show(
              notification.hashCode,
              notification.title,
              notification.body,
              NotificationDetails(
                android: AndroidNotificationDetails(
                  _channel.id,
                  _channel.name,
                  _channel.description,
                  icon: 'ic_launcher_foreground',
                  importance: Importance.max,
                  priority: Priority.max,
                ),
              ));
        }
      });

      final String fcmToken = await _messaging.getToken();
      print('fcmToken: $fcmToken');
    } else if (settings.authorizationStatus ==
        AuthorizationStatus.provisional) {
      print('User granted provisional permission');
    } else {
      print('User declined or has not accepted permission');
    }
  }
}

local_push.dart

import 'dart:convert';

import 'package:flutter/cupertino.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:nft/utils/app_log.dart';
import 'package:timezone/data/latest.dart' as tz;
import 'package:timezone/timezone.dart' as tz;

// Payload for notification
class NotyPayload {
  NotyPayload(this.type, {this.data});

  factory NotyPayload.fromJson(Map<String, dynamic> json) {
    return NotyPayload(json['type'] as int, data: json['data'] as String);
  }

  final int type;
  final String data;

  String toJson() => jsonEncode(<String, dynamic>{
        'type': type,
        'data': data,
      });

  @override
  String toString() {
    return 'NotyPayload{type: $type, data: $data}';
  }
}

class LocalPush {
  LocalPush._();

  static LocalPush I = LocalPush._();

  // Place this config in initState of Application Widget
  Future<bool> configInAppState({
    Future<void> Function(String payload) onSelectNotification,
    Future<void> Function(int id, String title, String body, String payload)
        onDidReceiveLocalNotification,
  }) {
    // Initialise the time zone database
    tz.initializeTimeZones();

    // Initialise setting
    final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
        FlutterLocalNotificationsPlugin();
    const AndroidInitializationSettings initializationSettingsAndroid =
        AndroidInitializationSettings('ic_launcher_foreground');
    final IOSInitializationSettings initializationSettingsIOS =
        IOSInitializationSettings(
            onDidReceiveLocalNotification: onDidReceiveLocalNotification);
    final InitializationSettings initializationSettings =
        InitializationSettings(
            android: initializationSettingsAndroid,
            iOS: initializationSettingsIOS);
    return flutterLocalNotificationsPlugin.initialize(initializationSettings,
        onSelectNotification: onSelectNotification);
  }

  static const String DEFAULT_SOUND = 'default';

  /// Unit generate notify id by string
  /// Format: key@group@extra
  /// Ex: stackId@reminder@2020/09/11
  int genNewNotifyId(String key, String group,
      {String extra = 'default'}) {
    return '$key@$group@$extra'.hashCode.abs();
  }

  /// About notify id: using 32bit integer.
  /// Note: can use hasCode of string to get int for id
  /// Format: '<payloadId>@<category>_millisecondsSinceEpoch'.hashCode
  /// Ex: 'stackId@reminder_millisecondsSinceEpoch'.hashCode
  Future<void> schedule(
      {@required int id,
      String title,
      @required String message,
      @required DateTime notifyAt,
      String sound = DEFAULT_SOUND,
      NotyPayload payload,
      int badgeNumber}) async {
    logger.d(
        'noti_schedule: id{$id} title{$title} message{$message} notifyAt{$notifyAt} sound{$sound}');
    final FlutterLocalNotificationsPlugin notifications =
        FlutterLocalNotificationsPlugin();
    // sound based on channel
    final AndroidNotificationDetails androidDetails =
        AndroidNotificationDetails(
      'primary_$sound',
      'Channel ${sound ?? 'no sound'}',
      'Notifications for ${sound ?? 'no sound'}',
      importance: Importance.max,
      priority: Priority.max,
      playSound: sound != null,
      sound: sound == DEFAULT_SOUND || sound == null
          ? null
          : RawResourceAndroidNotificationSound(sound),
    );
    final IOSNotificationDetails iOSDetails = IOSNotificationDetails(
        sound: sound == DEFAULT_SOUND || sound == null ? null : '$sound.aiff',
        presentAlert: true,
        presentBadge: true,
        presentSound: sound != null,
        badgeNumber: badgeNumber);
    final NotificationDetails platformChannelSpecifics =
        NotificationDetails(android: androidDetails, iOS: iOSDetails);

    if (notifyAt != null) {
      await notifications.zonedSchedule(
        id,
        title,
        message,
        tz.TZDateTime.from(notifyAt, tz.local),
        platformChannelSpecifics,
        androidAllowWhileIdle: true,
        uiLocalNotificationDateInterpretation:
            UILocalNotificationDateInterpretation.absoluteTime,
        payload: payload?.toJson(),
      );
    }
  }

  /// Cancel notification by id
  Future<void> cancel(int id) async {
    return FlutterLocalNotificationsPlugin().cancel(id);
  }

  /// Cancel all push notification
  Future<void> cancelAll() async {
    return FlutterLocalNotificationsPlugin().cancelAll();
  }
}

firebase_login.dart

import 'dart:async';
import 'package:apple_sign_in/apple_sign_in.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter_facebook_login/flutter_facebook_login.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:nft/utils/app_log.dart';

class AuthCanceled implements Exception {
  @override
  String toString() => 'AuthCanceled: cancelled by user';
}

class UserNotAuthorized implements Exception {
  @override
  String toString() => 'User not authorized!';
}

class FirebaseLogin {
  FirebaseLogin._();

  static final FirebaseLogin I = FirebaseLogin._();

  final FirebaseAuth _auth = FirebaseAuth.instance;
  final GoogleSignIn _googleSignIn = GoogleSignIn();
  final FacebookLogin _facebookLogin = FacebookLogin();

  Future<bool> isAppleSignInAvailable() => AppleSignIn.isAvailable();

  Future<User> signInGoogle() async {
    final GoogleSignInAccount googleUser = await _googleSignIn.signIn();
    if (googleUser == null) {
      return null;
    }
    final GoogleSignInAuthentication googleAuth =
        await googleUser?.authentication;

    final AuthCredential credential = GoogleAuthProvider.credential(
      accessToken: googleAuth.accessToken,
      idToken: googleAuth.idToken,
    );
    final User user = (await _auth.signInWithCredential(credential)).user;
    return user;
  }

  Future<User> signInApple() async {
    final AuthorizationResult loginResult = await AppleSignIn.performRequests([
      const AppleIdRequest(requestedScopes: [Scope.email, Scope.fullName])
    ]);
    switch (loginResult.status) {
      case AuthorizationStatus.authorized:
        // here we're going to sign in the user within firebase
        break;
      case AuthorizationStatus.error:
        // do something
        throw loginResult.error;

      case AuthorizationStatus.cancelled:
        throw AuthCanceled();
    }

    final AppleIdCredential appleIdCredential = loginResult.credential;

    final AuthCredential credential = OAuthProvider('apple.com').credential(
      accessToken: String.fromCharCodes(appleIdCredential.authorizationCode),
      idToken: String.fromCharCodes(appleIdCredential.identityToken),
    );

    final UserCredential result = await _auth.signInWithCredential(credential);
    final User user = result.user;
    logger.d('Signed in with apple ${user.uid}');
    return user;
  }


  Future<User> signInFacebook() async {
    final FacebookLoginResult result =
        await _facebookLogin.logIn(<String>['email']);
    final AuthCredential facebookAuthCred =
        FacebookAuthProvider.credential(result.accessToken.token);
    final User user = (await _auth.signInWithCredential(facebookAuthCred)).user;
    return user;
  }

  Future<void> logout() async {
    await _googleSignIn.disconnect();
    await _auth.signOut();
    await _facebookLogin.logOut();
  }
}

class UserSocialData {
  UserSocialData._({this.name, this.email, this.photoUrl});

  factory UserSocialData.fromApple(AuthorizationResult result, User user) {
    final AppleIdCredential appleIdCredential = result.credential;
    final PersonNameComponents nameComponents = appleIdCredential.fullName;
    final String name = <String>[
          nameComponents.namePrefix,
          nameComponents.givenName,
          nameComponents.middleName,
          nameComponents.familyName,
          nameComponents.nameSuffix,
        ].where((String element) => element != null).join(' ') ??
        'Apple ID';

    final String email =
        appleIdCredential.email ?? user?.providerData?.first?.email;
    return UserSocialData._(name: name, email: email, photoUrl: null);
  }

  factory UserSocialData.fromGoogle(GoogleSignInAccount account) {
    return UserSocialData._(
      name: account.displayName,
      email: account.email,
      photoUrl: account.photoUrl,
    );
  }

  factory UserSocialData.fromFacebook(UserCredential result) {
    final Map<String, dynamic> profile = result.additionalUserInfo.profile;
    final String name = profile['name'] as String ?? result.user.displayName;
    final String email = profile['email'] as String ?? result.user.email;
    final String pictureData =
        (profile['picture'] as Map<dynamic, dynamic>)['data'] as String;
    final String photoUrl =
        (pictureData as Map<dynamic, dynamic>)['url'] as String;
    return UserSocialData._(name: name, email: email, photoUrl: photoUrl);
  }

  final String name;
  final String email;
  final String photoUrl;
}

@nhancv

Leave a Reply

Your email address will not be published.Required fields are marked *