Flutter Firebase Телефонная аутентификация с BLoC - PullRequest
0 голосов
/ 25 октября 2019

Я пытаюсь добиться аутентификации телефона Firebase с шаблоном BLoC.

Это мой блок класса

class AuthBloc extends Bloc<AuthEvent, AuthState> {
  final AuthProvider authProvider;

  AuthBloc({this.authProvider}) : assert(authProvider!= null);

  @override
  AuthState get initialState => Uninitialized();

  @override
  Stream<AuthState> mapEventToState(AuthEvent event) async* {
    if (event is AppLaunched) {
      yield* _mapAppLaunchedToState();
    } else if(event is OtpRequested) {
      yield* _mapOtpRequestedToState();
    } else if (event is LoggedIn) {
      yield* _mapLoggedInToState();
    } else if (event is LoggedOut) {
      yield* _mapLoggedOutToState();
    }
  }

  Stream<AuthState> _mapAppLaunchedToState() async* {
    try {
      final isSignedIn = await authProvider.isLoggedIn();

      if (isSignedIn) {
        final name = userProvider.firebaseUser;
        yield Authenticated(name);
      } else {
        yield Unauthenticated();
      }
    } catch (_) {
      yield Unauthenticated();
    }
  }

  Stream<AuthState> _mapOtpRequestedTostate() async* {
    yield AuthInProgress();
    try {
      FirebaseUser firebaseUser = await authProvider.verifyPhone();

      if (firebaseUser != null) {
        yield Authenticated(firebaseUser);
      } else {
        yield Unauthenticated();
      }
    } catch(_, stacktrace) {
      yield Unauthenticated();
    }
  }

  Stream<AuthState> _mapLoggedInToState() async* {
    yield Authenticated(userProvider.firebaseUser);
  }

  Stream<AuthState> _mapLoggedOutToState() async* {
    yield Unauthenticated();
    authProvider.signOutUser();
  }
}

Это AuthProvider

class AuthProvider extends BaseAuthProvider {
  String _verificationId;
  FirebaseUser user;
  final FirebaseAuth _firebaseAuth;

  AuthProvider(
      {FirebaseAuth firebaseAuth})
      : _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance;

  @override
  Future<FirebaseUser> verifyPhone() async {
    final PhoneVerificationCompleted verificationCompleted =
        (AuthCredential phoneAuthCredential) async {
          user = (await _firebaseAuth.signInWithCredential(phoneAuthCredential)).user;
    };

    final PhoneVerificationFailed verificationFailed =
        (AuthException authException) {
      print(
          'Phone number verification failed. Code: ${authException.code}. Message: ${authException.message}');
    };

    final PhoneCodeSent codeSent =
        (String verificationId, [int forceResendingToken]) async {
      _verificationId = verificationId;
    };

    final PhoneCodeAutoRetrievalTimeout codeAutoRetrievalTimeout =
        (String verificationId) {
      _verificationId = verificationId;
    };

    await _firebaseAuth.verifyPhoneNumber(
        phoneNumber: _phoneNumberProvider.number,
        timeout: const Duration(seconds: 5),
        verificationCompleted: verificationCompleted,
        verificationFailed: verificationFailed,
        codeSent: codeSent,
        codeAutoRetrievalTimeout: codeAutoRetrievalTimeout);

    return user;
  }

  Future<FirebaseUser> signInWithPhone() async {
    final AuthCredential credential = PhoneAuthProvider.getCredential(
      verificationId: _verificationId,
      smsCode: _otpProvider.number,
    );
    final FirebaseUser user =
        (await _firebaseAuth.signInWithCredential(credential)).user;
    final FirebaseUser currentUser = await _firebaseAuth.currentUser();
    assert(user.uid == currentUser.uid);

    if (user != null) {
      return currentUser;
    } else {
      return null;
    }
  }

  @override
  Future<void> signOutUser() async {
    return Future.wait([_firebaseAuth.signOut()]); // terminate the session
  }

  @override
  Future<FirebaseUser> getCurrentUser() async {
    return await _firebaseAuth.currentUser(); //retrieve the current user
  }

  @override
  Future<bool> isLoggedIn() async {
    final user =
        await _firebaseAuth.currentUser(); //check if user is logged in or not
    return user != null;
  }

  @override
  void dispose() {}
}

Когдавызывается verifyPhone из AuthBloc, он выполняется асинхронно и, в свою очередь, вызывает mcallbacks, которые снова являются асинхронными. Таким образом, _mapOtpRequestedToState () будет завершено, прежде чем мы вернем FirebaseUser из AuthProvider. Следовательно, Аутентифицированное состояние не передается, и пользователь не входит в систему.

Требуется помощь !!!

1 Ответ

0 голосов
/ 26 октября 2019

Я думаю, что в большинстве случаев код, который можно прочитать, гораздо лучше.

В следующем примере реализована логика, которую вы намеревались написать, используя механизм (Action -> Event):

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Provider<AppStateBloc>(
      builder: (_) => AppStateBloc(),
      dispose: (_, bloc) {
        bloc.dispose();
      },
      child: MaterialApp(
        home: TestPage(),
      ),
    );
  }
}

class TestPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    AppStateBloc appStateBloc = Provider.of<AppStateBloc>(context, listen: false);

    return Scaffold(
      appBar: AppBar(title: Text('Flow Test')),
      body: Column(
        children: <Widget>[
          StreamBuilder<AppState>(
            stream: appStateBloc.stateOut,
            initialData: AppState.initial,
            builder: (BuildContext context, AsyncSnapshot<AppState> snapshot) {
              AppState state = snapshot.data;

              return Column(
                children: <Widget>[
                  Text('Current State: $state'),
                  SizedBox(height: 10.0),
                  if (state == AppState.initial || state == AppState.failure)
                    RaisedButton(
                      onPressed: () => appStateBloc.actionIn(AppStateAction.login),
                      child: Text('Authenticate'),
                    ),

                  if (state == AppState.authenticated)
                    RaisedButton(
                      onPressed: () => appStateBloc.actionIn(AppStateAction.logout),
                      child: Text('Logout'),
                    ),

                ],
              );
            },
          ),
        ],
      ),
    );
  }
}

class AppStateBloc {
  StreamController<AppState> _controllerState = StreamController<AppState>.broadcast();
  Stream<AppState> get stateOut => _controllerState.stream;
  Function(AppState) get _stateIn => _controllerState.sink.add;

  StreamController<AppStateAction> _controllerAction = StreamController<AppStateAction>.broadcast();
  Function(AppStateAction) get actionIn => _controllerAction.sink.add;

  StreamSubscription _subscription;

  AppStateBloc() {
    _subscription = _controllerAction.stream.listen(_businessLogic);
  }

  // All the business logic comes here
  void _businessLogic(AppStateAction action) async {
    switch (action) {

      case AppStateAction.login:
        // do authentication
        User user = await fakeAuthenticator.verifyUser();
        if (user == null) {
          _stateIn(AppState.failure);
        } else {
          _stateIn(AppState.authenticated);
        }
        break;

      case AppStateAction.logout:
        // do what needs to be done in this case
        await fakeAuthenticator.logout();
        _stateIn(AppState.initial);
        break;

      default:
        // nothing
        break;
    }
  }

  void dispose() {
    _subscription?.cancel();
    _controllerAction?.close();
    _controllerState?.close();
  }
}

enum AppStateAction {
  none,
  login,
  logout,
}

enum AppState {
  initial,
  authenticated,
  failure,
}

class User {}

class FakeAuthenticator {
  User _user;

  Future<User> verifyUser() async {
    // Simulation of Authentication made at server side
    await Future.delayed(const Duration(seconds: 1));

    // Successful authentication
    _user = User();

    return _user;
  }

  Future<void> logout() async {
    // Simulation of Authentication made at server side
    await Future.delayed(const Duration(seconds: 1));

    _user = null;
  }

  User get user => _user;

  // ------- Singleton
  static final FakeAuthenticator _instance = FakeAuthenticator._internal();
  factory FakeAuthenticator() => _instance;
  FakeAuthenticator._internal();
}

FakeAuthenticator fakeAuthenticator = FakeAuthenticator();

Основное отличие вашего кода в том, что с этим кодом, но это мое личное чувство, вы более «контролируете» свою бизнес-логику.

...