使用 flutter-fire 身份验证保护 Flutter 安全路由并避免不必要的重建

Flutter secure routes with a flutter-fire authentication guard and avoid unnecessary rebuilds

我目前面临一个问题,我将路由到身份验证保护视图作为我的默认路由。 我的鉴权守卫观点:

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

import '../models/user.dart';
import '../services/services.module.dart';
import '../widgets/common/async_stream.dart';
import 'landing_screen/landing_screen.dart';
import 'tabs_screen/tab_screen.dart';

/// The [ViewAuthGuard] decides whether to display the [LandingScreenView] or the [TabsScreenView].
class ViewAuthGuard extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print('ViewAuthGuard build called: $context');
    FirebaseAuthService authService = Provider.of<AuthService>(context, listen: false);

    return AsyncStreamWidget<User>(
      stream: authService.onAuthStateChanged,
      child: (User user) => TabsScreenView(),
      emptyWidget: LandingScreenView(),
      loadingWidget: null,
      errorWidget: null,
    );
  }
}

和我的 AsyncStreamWidget:

import 'package:flutter/material.dart';

import '../../../models/base_model.dart';
import '../../error/future_error.dart';
import '../../loading.dart';

class AsyncStreamWidget<T extends BaseModel> extends StatelessWidget {
  final Stream<T> stream;
  final T initialData;

  Widget _loading;
  Widget _empty;
  Widget Function(Object) _error;
  Widget Function(T) child;

  AsyncStreamWidget({
    @required this.stream,
    @required this.child,
    this.initialData,
    Widget loadingWidget,
    Widget emptyWidget,
    Widget Function(Object) errorWidget,
  }) {
    if (loadingWidget == null) {
      _loading = Loading();
    } else {
      _loading = loadingWidget;
    }

    if (errorWidget == null) {
      _error = (Object error) => FutureErrorWidget(error: error);
    } else {
      _error = errorWidget;
    }

    if (emptyWidget == null) {
      _empty = Center(child: Text('No data available.'));
    } else {
      _empty = emptyWidget;
    }
  }

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<T>(
      initialData: initialData,
      stream: stream,
      builder: (_, AsyncSnapshot<T> snapshot) {
        switch (snapshot.connectionState) {
          case ConnectionState.waiting:
            return _loading;
            break;
          case ConnectionState.active: // check if different behavior is needed for active and done
          case ConnectionState.done:
            // error state
            if (snapshot.hasError) {
              // todo more throughout error checking and specialized error widget
              return _error(snapshot.error);
            }

            // data state
            if (snapshot.hasData) {
              T data = snapshot.data;
              return child(data);
            }

            // empty state
            return _empty;
          case ConnectionState.none:
          default:
            print('E: Received Future [$stream] was null or else.');
            return _error('Unknown error.');
        }
      },
    );
  }
}

FirebaseAuthService 包裹 auth.FirebaseAuth.instance。我的流构造如下:

User _userFromFirebase(auth.User user) {
  if (user == null) {
    return null;
  }
  return User(
    uid: user.uid,
    email: user.email,
    displayName: user.displayName,
    photoUrl: user.photoURL,
  );
}

@override
Stream<User> get onAuthStateChanged => _firebaseAuth.authStateChanges().map(_userFromFirebase);

我目前提供 ViewAuthGuard 以上的所有服务。

我用 ThemeProvider ChangeNotifier 包装了我的 Material 应用程序(以防出现问题)。

我的问题是 ViewAuthGuard 下的所有小部件都已重建并且它们的状态已重置。这是我在开发时发生的。发生热重载时,所有 children 都会重建。 TabsScreenView 包含我的 flutter 应用程序的初始导航,并且在开发过程中始终重置为索引零。

问题:此时如何避免不必要的重新加载?

到目前为止我测试了什么:

如果您需要更多信息、代码、控制台打印或支持我,请给我留言。谢谢!

我能够自己修复它。对于任何感兴趣的人,这是过程:

stream 是决定 AsyncStreamWidget 是否重建其子项的枢轴元素。因此,我确实通过打印其 hashCode 属性来检查 stream 是否在热重载期间发生了变化(是的,它确实发生了变化)。

随后,我确实将我的 ViewAuthGuard 更改为 StatefulWidget 并使用 didChangeDependencies 方法将 stream 存储在状态中。

(我也开始在 initState 方法中实例化小部件并将它们也存储在状态中,以便它们只创建一次。)


EDIT 我应该提到我的 ViewAuthGuard 没有依赖项。所以它不会因为依赖关系的变化而重建。