Мультипровайдер и абстрактный класс

#flutter #dart #provider #bloc

#flutter #dart #поставщик #блок

Вопрос:

Добрый день!

Как я могу использовать поставщика для MaterialApp? У меня есть многопоставщик и абстрактный класс. Необходимо передать аутентификацию на целевую страницу

Вот что я хотел бы получить:

  Widget build(BuildContext context) {
    return Provider<AuthBase>(
      create: (context) => Auth(),
      child: MaterialApp(
        title: "Bloc Test",
        theme: ThemeData(primarySwatch: Colors.indigo),
        home: LandingPage(),
      ),
    );
  }
 

Вот мой рабочий код:

  class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MultiProvider(
          providers: [
            ChangeNotifierProvider<ToDoProvider>(
              create: (ctx) => ToDoProvider(),
            ),
          ],
          child: MaterialApp(
            debugShowCheckedModeBanner: false,
            title: 'Ultimative ToDo',
            theme: ThemeData(
              scaffoldBackgroundColor: myListMainColor,
            textTheme:
                  GoogleFonts.sourceSansProTextTheme(Theme.of(context).textTheme),
              visualDensity: VisualDensity.adaptivePlatformDensity,
             
            ),
            initialRoute: '/',
            routes: {
              '/': (context) => LandingPage(auth: Auth()),
              OpenedToDo.routeName: (context) => OpenedToDo(),
             },
          ),
 

У меня есть абстрактный класс AuthBase, но я не могу смешать его с ChangeNotifier. Так что это причина, по которой я не могу поместить новую строку кода в мультипровайдер.

 abstract class AuthBase {
  User get currentUser;
  Future<User> signInAnonymously();
  Stream<User> authStateChanges();
  Future<void> singOut(BuildContext context);
  Future<User> singInWithGoogle();
  Future<User> createUserWithEmailAndPassword(String email, String password);
  Future<User> signInWithEmailAndPassword(String email, String password);
}

class Auth implements AuthBase {
  final _firebaseAuth = FirebaseAuth.instance;

  @override
  Stream<User> authStateChanges() => _firebaseAuth.authStateChanges();

  @override
  User get currentUser => _firebaseAuth.currentUser;

  @override
  Future<User> signInAnonymously() async {
  
  }

  @override
  Future<User> signInWithEmailAndPassword(String email, String password) async {
   
  }

  @override
  Future<User> createUserWithEmailAndPassword(
   
  }

  @override
  Future<User> singInWithGoogle() async {
  
  }

  @override
  Future<void> singOut(BuildContext context) async {
   
  }
}
 

Ошибка:

Ошибка: не удалось найти правильного поставщика над этим виджетом StreamBuilder

Вероятно, это происходит потому, что вы использовали a BuildContext , который не включает поставщика по вашему выбору. Существует несколько распространенных сценариев:

  • The provider you are trying to read is in a different route.

    Providers are «scoped». So if you insert of provider inside a route,
    then other routes will not be able to access that provider.

  • You used a BuildContext that is an ancestor of the provider you are trying to read.

    Убедитесь, что StreamBuilder находится под вашим многопоставщиком / провайдером. Обычно это происходит, когда вы создаете поставщика и пытаетесь его немедленно прочитать.

    Например, вместо:

       return Provider<Example>(
        create: (_) => Example(),
        // Will throw a ProviderNotFoundError, because `context` is associated
        // to the widget that is the parent of `Provider<Example>`
        child: Text(context.watch<Example>()),
      ),   }   ```
    
    consider using `builder` like so:
    
    ```   Widget build(BuildContext context) {
      return Provider<Example>(
        create: (_) => Example(),
        // we use `builder` to obtain a new `BuildContext` that has access to the provider
        builder: (context) {
          // No longer throws
          return Text(context.watch<Example>()),
        }
      ),   }   ```
     

Целевая страница:

  @override
  Widget build(BuildContext context) {
    return StreamBuilder(
      stream: auth.authStateChanges(),
      builder: (context, snapshot) {
        //* если подключился к данным
        if (snapshot.connectionState == ConnectionState.active) {
          //* получаем данные о пользователе
          final User user = snapshot.data;

          print('~ uid is ${user?.uid}');

          if (user == null) {
            //Navigator.of(context).pushNamed(SignInPage.routeName, arguments: auth);

            return SignInPage.create(context);
          } else {
            // Navigator.of(context).pushNamed(HomeScreen.routeName);
            return HomeScreen();
          }
        }
        return Scaffold(
          body: Center(
            child: CircularProgressIndicator(),
          ),
        );
      },
    );
  }
 

На странице входа есть эта часть кода:

  static Widget create(BuildContext context) {
    final auth = Provider.of<AuthBase>(context, listen: false);
    return Provider<SignInBloc>(
      create: (_) => SignInBloc(auth: auth),
      //* обязательно должен быть dispose
      dispose: (_, bloc) => bloc.dispose(),
      //* consumer помогает прокинуть данные в конструктор
      child: Consumer<SignInBloc>(
        child: SignInPage(),
        builder: (_, bloc, __) => SignInPage(bloc: bloc),
      ),
    );
  }
 

Ответ №1:

Прежде чем перейти к абстрактной теме вопроса, я бы рекомендовал запустить MultiProvider в основной папке. Вот так

 import 'package:provider/provider.dart' as provider;

void main() {
  runApp(provider.MultiProvider(
    providers: [
      provider.ChangeNotifierProvider.value(value: Prov1()),
      provider.ChangeNotifierProvider.value(value: Prov2()),
      provider.ChangeNotifierProvider.value(value: Prov3()),
      provider.ChangeNotifierProvider.value(value: Prov4()),
    ],
    child: MyApp(),
  ));
}
 

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

Комментарии:

1. Игорь, спасибо за ваш ответ. Одна вещь, которую я не могу понять, что такое провайдер? Как и где его объявить?

2. Я только что обновил ответ, в который я добавил импорт поставщика, уточнив, откуда он взялся! Надеюсь, это поможет.

3. Игорь, большое спасибо! Я просто забыл об именовании импорта.

Ответ №2:

Хорошо, я нашел, как это решить. Это довольно просто. Итак, в main.dart в разделе кода MultiProvider я должен добавить только одну строку:

  class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MultiProvider(
          providers: [
        ChangeNotifierProvider<ToDoProvider>(
          create: (ctx) => ToDoProvider(),
        ),
        THIS CODE ==> Provider<AuthBase>.value(value: Auth()),
      ],
      child: MaterialApp(
...
 

LandingPage.dart:

 class LandingPage extends StatelessWidget {
  static const routeName = '/landingPage';

  @override
  Widget build(BuildContext context) {
    final auth = Provider.of<AuthBase>(context, listen: false);
    return StreamBuilder(
      stream: auth.authStateChanges(),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.active) {
          final User user = snapshot.data;
          print('~ uid is ${user?.uid}');
            if (user == null) {
            return SignInPage.create(context);
          } else {
            // Navigator.of(context).pushNamed(HomeScreen.routeName);
            return HomeScreen();
          }
        }
        return Scaffold(
          body: Center(
            child: CircularProgressIndicator(),
          ),
        );
      },
    );
  }
}
 

И в SignInPage.dart:

 static Widget create(BuildContext context) {
    final auth = Provider.of<AuthBase>(context, listen: false);
    return Provider<SignInBloc>(
      create: (_) => SignInBloc(auth: auth),
      dispose: (_, bloc) => bloc.dispose(),
      child: Consumer<SignInBloc>(
        child: SignInPage(),
        builder: (_, bloc, __) => SignInPage(bloc: bloc),
      ),
    );
  }
 

Итак, поскольку AuthBase является абстрактным классом, я не могу смешивать его с ChangeNotifier, но мне это не нужно, потому что этот класс не является изменяемым. Вот почему я использую Provider.value(значение: значение).