Поток FirebaseAuth.instance.userChanges не выдает пользователя, вошедшего в систему, после перезагрузки страницы (веб)

# #firebase #flutter #dart #flutter-web

Вопрос:

В Flutter Web поток изменений пользователей из экземпляра FirebaseAuth никогда не выдает пользователя, вошедшего в систему, после перезагрузки страницы. Вместо этого он выдает только значение null. В приведенном ниже примере приложение застревает на странице загрузки.

 import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(App());
}

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: '/loading',
      onGenerateRoute: (settings) {
        switch (settings.name) {
          case '/error':
            return MaterialPageRoute(builder: (_) => ErrorScreen());
          case '/loading':
            return MaterialPageRoute(builder: (_) => LoadingScreen());
          case '/signin':
            return MaterialPageRoute(builder: (_) => SignInScreen());
          case '/welcome':
            return MaterialPageRoute(builder: (_) => WelcomeScreen());
          default:
            return MaterialPageRoute(
                builder: (_) => Center(child: Text('Unknown route')));
        }
      },
    );
  }
}

class ErrorScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Center(
        child: Text('An error occurred.'),
      ),
    );
  }
}

class LoadingScreen extends StatefulWidget {
  @override
  _LoadingScreenState createState() => _LoadingScreenState();
}

class _LoadingScreenState extends State<LoadingScreen> {
  @override
  void initState() {
    init();
    super.initState();
  }

  init() async {
    try {
      await Firebase.initializeApp();
      if (kDebugMode) {
        await FirebaseAuth.instance.useEmulator('http://localhost:1001');
      }

      final preferences = await SharedPreferences.getInstance();
      bool signedIn = preferences.getBool('IS_SIGNED_IN') ?? false;

      String landingPath = '/signin';

      if (signedIn) {
        landingPath = '/welcome';
        // Wait for the userChanges to emit a non-null element.
        await FirebaseAuth.instance
            .userChanges()
            .firstWhere((user) => user != null);
      }

      Navigator.of(context).pushReplacementNamed(landingPath);
    } catch (error) {
      print(error);
      Navigator.of(context).pushReplacementNamed('/error');
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Center(
        child: Text('Loading...'),
      ),
    );
  }
}

class SignInScreen extends StatelessWidget {
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          children: [
            TextField(
              controller: _emailController,
              decoration: InputDecoration(labelText: 'Email address'),
            ),
            TextField(
              decoration: InputDecoration(labelText: 'Password'),
              obscureText: true,
              controller: _passwordController,
            ),
            ElevatedButton(
              onPressed: () => _submit(context),
              child: Text('SIGN IN'),
            ),
          ],
        ),
      ),
    );
  }

  void _submit(BuildContext context) async {
    final email = _emailController.text;
    final password = _passwordController.text;
    FirebaseAuth.instance
        .signInWithEmailAndPassword(email: email, password: password)
        .then((credential) async {
      final preferences = await SharedPreferences.getInstance();
      await preferences.setBool('IS_SIGNED_IN', true);

      Navigator.of(context).pushReplacementNamed('/welcome');
    });
  }
}

class WelcomeScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          children: [
            Text('Welcome!'),
            ElevatedButton(
              onPressed: () => _signOut(context),
              child: Text('SIGN OUT'),
            )
          ],
        ),
      ),
    );
  }

  void _signOut(BuildContext context) {
    FirebaseAuth.instance.signOut().then((_) async {
      final preferences = await SharedPreferences.getInstance();
      await preferences.setBool('IS_SIGNED_IN', false);

      Navigator.of(context).pushReplacementNamed('/signin');
    });
  }
}
 

Если это поможет, я использую версию 8.3.0 библиотек Javascript Firebase в своем index.html файл.

Я что-то здесь упускаю?

Ответ №1:

Оказывается, приведенный выше код в порядке. Проблема в эмуляторе. Комментируя эти строки кода, приложение ведет себя так, как ожидалось:

 if (kDebugMode) {
  FirebaseAuth.instance.useEmulator('http://localhost:1001');
}
 

Я подал сообщение об ошибке в репо FlutterFire по поводу этой проблемы с эмулятором.