Многосторонняя аутентификация во Flutter с помощью flutter_bloc


Эта статья изначально была опубликована в блоге Codemagic.

В этом руководстве Годвин Александр Экайну покажет вам, как добавить аутентификацию Google для разных вкусов в ваше приложение Flutter. В этом уроке мы будем использовать VS Code.

Введение

В этом уроке мы познакомимся с определениями различных этапов производства и их значением в разработке. Мы также рассмотрим, как использовать различные конфигурации Firebase в различных средах. Кроме того, мы узнаем о блочных виджетах и о том, что они делают при управлении состоянием в нашем Flutter-приложении.

Мы создадим проект Flutter для реализации аутентификации Google для различных вкусов, чтобы мы могли иметь разные сборки одного и того же приложения на наших устройствах для тестирования.

Различные стадии

В крупных технологических компаниях проекты обычно выполняются в три этапа: разработка, постановка и производство (хотя в разных компаниях эти этапы могут отличаться).

На этих различных этапах продукты тщательно тестируются для устранения ошибок, чтобы высококачественные программные продукты могли быть отправлены потребителю.

Разработка

На этом этапе в среде разработки происходит первоначальное создание пользовательского интерфейса и интеграция API и бэк-энда. Данные, с которыми вы работаете в этой среде, обычно представляют собой тестовый API или тестовую базу данных, и ни одна из этих данных не является реальной. Если после выпуска приложения необходимо добавить в него новые функции, они сначала внедряются из среды разработки.

На этом этапе проводится множество тестов кода, чтобы убедиться, что код полностью функционален и приложение работает эффективно. Тип тестирования, проводимого на этом этапе, называется unit testing.

Staging

В среде постановки можно привлечь отдельных пользователей для тестирования приложения. Это может дать вам хорошее представление о том, как приложение будет работать после запуска, поскольку оно может взаимодействовать с реальными данными. Среда тестирования пытается имитировать производство, поэтому даже в случае серьезного недостатка и поломки системы производство не придется останавливать.

На этом этапе тестируются все миграции баз данных. Функции также тестируются для проверки наихудших сценариев при добавлении новых функций. Если новые функции ломаются при продвижении, ошибки обнаруживаются и исправляются.

Если вы пользовались WhatsApp Web, вам, скорее всего, предлагали присоединиться к программе тестирования, чтобы проверить новую функцию до того, как она станет официальной. Это пример того, что известно как бета-тестирование.

Производство

Это этап, на котором приложение запускается для опробования пользователями. Это самый важный этап для вашей компании или клиента. На этапе производства вы не хотите, чтобы пользователи заметили какие-либо серьезные ошибки, так как в итоге вы можете потерять пользователей. В идеале к этому моменту большинство серьезных ошибок в программном обеспечении будет устранено на предыдущих этапах.

Общий совет по разработке: не стоит сразу внедрять все функции программы. Приоритетнее держать проблемы под контролем и обеспечивать стабильность текущих функций, прежде чем внедрять новые.

Сборка нашего приложения Flutter

Для настройки вкусов в нашем приложении Flutter потребуется ручная настройка, что означает, что нам придется работать с множеством файлов в разных каталогах. Это может стать очень запутанным, но с помощью very_good_cli все можно сделать намного проще.

Установка

Для создания нового проекта Flutter мы будем использовать пакет Dart под названием very_good_cli. Этот пакет поможет нам создать стандартную структуру и настроить все окружения, необходимые в нашем приложении как для iOS, так и для Android.

Чтобы установить инструмент, введите в терминале следующую команду: dart pub global activate very_good_cli.

После установки и активации very_good_cli мы можем использовать следующую команду для создания проекта Flutter: very_good create my_app --desc "My new Flutter app" --org "com.custom.org".

Замените содержимое вашего YAML-файла на следующее:

name: googlesigninwithflavor
description: A Very Good Project created by Very Good CLI.
version: 1.0.0+1
publish_to: none
environment:
  sdk: ">=2.16.0 <3.0.0"
dependencies:
  bloc: ^8.0.3
  flutter:
    sdk: flutter
  flutter_bloc: ^8.0.1
  intl: ^0.17.0
  google_sign_in: ^5.2.4
  equatable: ^2.0.3
  firebase_core: ^1.14.0
  firebase_auth: ^3.3.13
dev_dependencies:
  bloc_test: ^9.0.3
  flutter_test:
    sdk: flutter
  mocktail: ^0.3.0
  very_good_analysis: ^2.4.0
flutter:
  uses-material-design: true
  generate: true
  assets:
    - asset/

Войти в полноэкранный режим Выйти из полноэкранного режима

Структура файла

lib
├─ app
│  ├─ bloc
│  │  ├─ google_sign_in_bloc.dart
│  │  ├─ google_sign_in_event.dart
│  │  └─ google_sign_in_state.dart
│  ├─ view
│  │  ├─ app.dart
│  │  └─ google_sign_in_view.dart
│  └─ app.dart
├─ dashboard
│  └─ dashboard.dart
├─ repository
│  └─ authentication_repository.dart
├─ bootstrap.dart
├─ generated_plugin_registrant.dart
├─ main_development.dart
├─ main_production.dart
└─ main_staging.dart

Вход в полноэкранный режим Выход из полноэкранного режима

Когда мы создаем наш проект, мы видим три разных основных файла, названных в соответствии с тремя этапами производства. Каждый из этих файлов может иметь различные настройки, необходимые на данном этапе производства.

main_development.dart
main_production.dart
main_staging.dart

Вход в полноэкранный режим Выход из полноэкранного режима

Мы можем запустить любой флавор, передав в терминал следующие аргументы:

# For Development
flutter run --flavor development --t lib/main_development.dart
# For Staging
flutter run --flavor development --t lib/main_staging.dart
# For Production
flutter run --flavor development --t lib/main_production.dart

Войти в полноэкранный режим Выйти из полноэкранного режима

Для Android файлом, с которым мы обычно работаем во время установки различных вкусов, является файл build.gradle в нашей директории Android.

Для iOS файлы, с которыми мы будем работать, — это схемы Xcode. Однако нам не понадобится этого делать, поскольку они уже сгенерированы за нас very_good_cli.

Настройка Firebase для наших различных окружений

Недавно Firebase представила Firebase CLI, который сделал настройку проектов Firebase простой и удобной. Однако есть некоторые функции, которые он пока не поддерживает, например, аналитика и Google Sign-In. Поскольку мы будем использовать Google Sign-In, нам необходимо вручную настроить Firebase. Поэтому мы будем вручную настраивать наши проекты Firebase.

В нашей консоли Firebase мы создадим три различных проекта следующим образом:

  • flavor dev
  • flavor stg
  • flavor prod

При регистрации нашего проекта Flutter в Firebase мы должны добавить .dev, .stg или .prd к имени нашего пакета Android.

Имя пакета можно найти в файле android/app/build.gradle. Оно отображается как applicationId, как показано ниже.

 defaultConfig {
        applicationId "com.example.verygoodcore.googlesigninwithflavor"
        minSdkVersion flutter.minSdkVersion
        targetSdkVersion flutter.targetSdkVersion
        versionCode flutterVersionCode.toInteger()
        versionName flutterVersionName
    }
    flavorDimensions "default"
    productFlavors {
        production {
            dimension "default"
            applicationIdSuffix ""
            manifestPlaceholders = [appName: "Googlesigninwithflavor"]
        }
        staging {
            dimension "default"
            applicationIdSuffix ".stg"
            manifestPlaceholders = [appName: "[STG] Googlesigninwithflavor"]
        }
        development {
            dimension "default"
            applicationIdSuffix ".dev"
            manifestPlaceholders = [appName: "[DEV] Googlesigninwithflavor"]
        }
    }

Вход в полноэкранный режим Выход из полноэкранного режима

Для iOS

При регистрации для iOS нам также необходимо ввести правильный идентификатор пакета. Однако в Xcode может быть трудно ориентироваться, если у вас нет опыта работы с ним. Мы можем найти идентификатор пакета, выполнив следующие действия:

  • Откройте Xcode 

  • Нажмите на Open a project or file.

  • Перейдите к своему проекту Flutter и откройте каталог iOS. Выберите директорию Runner.xcworkspace.

  • Далее нажмите на выпадающий список Runner. На вкладке General View выберите Build Settings, и мы найдем наш пакет приложений прямо там. 

Мы повторим эти шаги для всех наших проектов Firebase. После регистрации мы можем загрузить файл google-services.json для Android и GoogleService-Info.plist для iOS.

Файлы google-services.json будут перемещены в android/app/src/{перспективная среда}. Для iOS мы создадим новый каталог config, содержащий подкаталоги наших окружений, и добавим GoogleService-Info.plist в различные окружения. Мы добавим этот файл в Runner в Xcode. Прочитайте это руководство по Firebase для получения дополнительной информации.

Настройка для реализации аутентификации Google

Чтобы использовать аутентификацию Google в нашем приложении, нам нужно включить Google Provider, выполнив следующие шаги. Мы будем включать Google Provider в нашей среде разработки.

  • Перейдите в Firebase. В этом руководстве мы начнем с проекта разработки.

  • На главной панели навигации выберите Аутентификация. Вы должны увидеть следующее: 

  • Далее нажмите на Set up sign-in method. Отобразится список провайдеров. Выберите Google Sign-In, после чего вы увидите следующее: 

  • Включите Enable и добавьте электронную почту поддержки для проекта, как показано на изображении ниже. Затем сохраните настройки. 

  • На панели навигации нажмите на значок настроек и выберите Настройки проекта из выпадающего списка. Затем прокрутите страницу в самый низ. Нам нужно добавить SHA-1 ключ и SHA-256 ключ из нашего проекта.

  • Чтобы добавить ключи SHA или отпечатки пальцев, мы вернемся к нашему проекту, щелкнем правой кнопкой мыши на папке Android и нажмем Открыть в интегрированном терминале. Это должно открыть для нас новый терминал в нашей среде VS Code.

  • В терминале используйте команду ./gradlew signingReport для получения ключей. После выполнения этой команды мы должны получить различные ключи подписи из-за нескольких окружений. Используйте отладочные ключи SHA-1 в терминале.

  • Альтернативный вариант: В терминале вашего редактора кода вы можете изменить каталог на папку Android и выполнить команду. 

  • Нажмите кнопку сохранения и загрузите обновленный файл google-services.json. Замените файл google-services.json в вашей среде разработки. В нашем случае мы будем добавлять его в среду разработки.

Примечание: Ключ SHA-1 необходим Firebase для правильной работы Google Sign-In.

Мы только что включили аутентификацию Google! Мы реализуем ее в следующем разделе.

Реализация аутентификации Google в нашем проекте Flutter

Для управления состояниями мы будем использовать Bloc. Bloc — это менеджер состояний, добавленный very_good_cli.

Создание репозитория

Репозиторий — это как сервис, который обслуживает наш блок с данными, которые мы должны отправлять в наш слой пользовательского интерфейса. Репозиторий отвечает за выполнение прямых вызовов API по запросу блока. Мы реализуем наш репозиторий следующим образом:

import 'dart:developer';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
class AuthenticationRepository {
  Future<bool> signInWithGoogle() async {
    try {
      final user = GoogleSignIn().currentUser ?? await GoogleSignIn().signIn();
      if (user != null) {
        await GoogleSignIn().signOut();
      }
      final googleAuth = await user?.authentication;
      final credential = GoogleAuthProvider.credential(
        accessToken: googleAuth?.accessToken,
        idToken: googleAuth?.idToken,
      );
      await FirebaseAuth.instance.signInWithCredential(credential);
      return true;
    } catch (e) {
      log('An Error Occurred $e');
      return false;
    }
  }

  Future<void> handleSignOut() => GoogleSignIn().disconnect();
  // Future<void> signOut() async {
  //   final _googleSignIn = GoogleSignIn();
  //   _googleSignIn.disconnect();
  // }
}

Вход в полноэкранный режим Выход из полноэкранного режима

События блока

Для нашего проекта нам понадобятся два события, GoogleSignInRequested и GoogleSignOutRequested. Мы можем реализовать это следующим образом:

part of 'google_sign_in_bloc.dart';
abstract class GoogleSignInEvent extends Equatable {
  const GoogleSignInEvent();
  @override
  List<Object> get props => [];
}
class GoogleSignInRequested extends GoogleSignInEvent {}
class GoogleSignOutRequested extends GoogleSignInEvent {}

Вход в полноэкранный режим Выход из полноэкранного режима

Создание блочного состояния

Нам также понадобится два состояния — одно для случая, когда пользователь Authenticated и другое для случая, когда пользователь UnAuthenticated:

part of 'google_sign_in_bloc.dart';
abstract class GoogleSignInState extends Equatable {
  @override
  List<Object?> get props => [];
}
class UnAuthenticated extends GoogleSignInState {}
class Authenticated extends GoogleSignInState {}

Вход в полноэкранный режим Выход из полноэкранного режима

Создание нашего блока

Наш блок будет отображать два состояния в пользовательском интерфейсе: Authenticated, когда пользователь вошел в систему, и UnAuthenticated, когда пользователь вышел из приборной панели.

Мы реализуем это следующим образом:

import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:googlesigninwithflavor/repository/authentication_repository.dart';
part 'google_sign_in_event.dart';
part 'google_sign_in_state.dart';
class GoogleSignInBloc extends Bloc<GoogleSignInEvent, GoogleSignInState> {
  GoogleSignInBloc({required this.authenticationRepository})
      : super(UnAuthenticated()) {
    on<GoogleSignInRequested>(_onGoogleSignInPressed);
    on<GoogleSignOutRequested>(_onGoogleSignOutPressed);
  }
  final AuthenticationRepository authenticationRepository;
  Future<void> _onGoogleSignInPressed(
    GoogleSignInRequested event,
    Emitter<GoogleSignInState> emit,
  ) async {
    final response = await authenticationRepository.signInWithGoogle();
    if (response) {
      emit(Authenticated());
    }
  }
  void _onGoogleSignOutPressed(
    GoogleSignOutRequested event,
    Emitter<GoogleSignInState> emit,
  ) {
    authenticationRepository.handleSignOut();
    emit(UnAuthenticated());
  }
}

Войти в полноэкранный режим Выход из полноэкранного режима

Создание нашего пользовательского интерфейса

Наш класс app.dart будет отображать наш домашний экран следующим образом:

import 'package:flutter/material.dart';
import 'package:googlesigninwithflavor/app/view/google_sign_in_view.dart';
class App extends StatelessWidget {
  const App({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        appBarTheme: const AppBarTheme(color: Color(0xFF13B9FF)),
        colorScheme: ColorScheme.fromSwatch(
          accentColor: const Color(0xFF13B9FF),
        ),
      ),
      home: const GoogleSignInView(),
    );
  }
}

Вход в полноэкранный режим Выход из полноэкранного режима

google_sign_in_view.dart будет содержать реализацию для нашего домашнего экрана и кнопку входа.

class GoogleSignInView extends StatelessWidget {
  const GoogleSignInView({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return RepositoryProvider(
      create: (context) => AuthenticationRepository(),
      child: BlocProvider(
        create: (context) => GoogleSignInBloc(
          authenticationRepository: RepositoryProvider.of(context),
        ),
        child: Scaffold(
          appBar: AppBar(
            title: const Text('Sign In With Google'),
          ),
          body: const ShowSignInButton(),
        ),
      ),
    );
  }
}

Вход в полноэкранный режим Выход из полноэкранного режима

В приведенном выше коде мы использовали виджет RepositoryProvider для создания экземпляра нашего хранилища и дочернего элемента, который даст нам доступ к хранилищу через RepositoryProvider.of(context).

Мы также использовали виджет BlocProvider для создания экземпляра нашего Bloc, чтобы к нему могли обращаться виджеты поддерева и дочерних элементов.

Далее мы будем использовать блок в наших дочерних виджетах следующим образом:

class ShowSignInButton extends StatelessWidget {
  const ShowSignInButton({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return BlocListener<GoogleSignInBloc, GoogleSignInState>(
      listener: (context, state) {
        if (state is Authenticated) {
          Navigator.push<Type>(
            context,
            MaterialPageRoute(builder: (_) => const DashBoard()),
          );
        }
      },
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Center(
            child: Image.asset(
              'asset/google2.png',
              height: 60,
            ),
          ),
          ElevatedButton(
            onPressed: () {
              context.read<GoogleSignInBloc>().add(GoogleSignInRequested());
            },
            child: const Text('Sign In With Google'),
          ),
        ],
      ),
    );
  }
}

Войти в полноэкранный режим Выйти из полноэкранного режима

Здесь мы использовали BlocListner для перехода на другой экран на основе состояния, излучаемого блоком.

Мы также передали событие GoogleSignInRequested нашей ElevatedButton, поэтому, когда пользователь нажимает кнопку для входа в Google, событие передается блоку, и делается запрос к API GoogleSignIn.

Давайте также реализуем экран приборной панели для отображения данных аутентифицированного пользователя. Чтобы реализовать это, нам нужно будет вызвать экземпляр Firebase Auth для доступа к данным текущего пользователя. Мы также создадим кнопку выхода, которая вернет пользователя на экран входа в систему с помощью виджета BlocListner:

class DashBoard extends StatelessWidget {
  const DashBoard({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return RepositoryProvider(
      create: (context) => AuthenticationRepository(),
      child: BlocProvider(
        create: (context) => GoogleSignInBloc(
          authenticationRepository: RepositoryProvider.of(context),
        ),
        child: DashBoardDetails(),
      ),
    );
  }
}
class DashBoardDetails extends StatelessWidget {
  DashBoardDetails({
    Key? key,
  }) : super(key: key);
  final user = FirebaseAuth.instance.currentUser!;
  @override
  Widget build(BuildContext context) {
    return BlocListener<GoogleSignInBloc, GoogleSignInState>(
      listener: (context, state) {
        if (state is UnAuthenticated) {
          Navigator.of(context).pushAndRemoveUntil<Type>(
            MaterialPageRoute(builder: (context) => const GoogleSignInView()),
            (route) => false,
          );
        }
      },
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Very Good Codemagic'),
        ),
        body: BlocBuilder<GoogleSignInBloc, GoogleSignInState>(
          builder: (context, state) {
            return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Center(
                  child: Text('Welcome to your Dashboard'),
                ),
                Text('${user.displayName}'),
                if (user.photoURL != null)
                  Image.network('${user.photoURL}')
                else
                  Container(),
                ElevatedButton(
                  onPressed: () {
                    context
                        .read<GoogleSignInBloc>()
                        .add(GoogleSignOutRequested());
                  },
                  child: const Text('Sign Out'),
                )
              ],
            );
          },
        ),
      ),
    );
  }
}

Вход в полноэкранный режим Выход из полноэкранного режима

Ура! 🎉 Мы только что успешно создали аутентификацию Google Sign-In для наших сред разработки, постановки и производства.

Ресурсы

  • Узнайте, как вручную использовать Flutter flavors
  • Flutter готов к работе
  • very_good_cli
  • GitHub

Оцените статью
Procodings.ru
Добавить комментарий