MultiProvider и абстрактный класс

Добрый день!

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

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

 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. Вот почему я не могу поместить новую строку кода в MultiProvider.

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.

Скорее всего, это происходит из-за того, что вы использовали BuildContext, в котором отсутствует выбранный вами поставщик. Есть несколько распространенных сценариев:

  • Провайдер, которого вы пытаетесь прочитать, находится на другом маршруте.

    Провайдеры ограничены. Поэтому, если вы вставите поставщика внутри маршрута, другие маршруты не смогут получить доступ к этому поставщику.

  • Вы использовали BuildContext, который является предком провайдера, которого вы пытаетесь прочитать.

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

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

      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>()),
        }
      ),   }   ```
    

LandingPage:

 @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(),
          ),
        );
      },
    );
  }

SignInPage имеет эту часть кода:

 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),
      ),
    );
  }

person Vladimir    schedule 22.12.2020    source источник


Ответы (2)


Прежде чем перейти к абстрактной теме вопроса, я бы порекомендовал запустить 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(),
  ));
}

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

person Igor L Sambo LSambo    schedule 22.12.2020
comment
Игорь, спасибо за ответ. Одна вещь, которую я не могу понять, что такое провайдер? Как и где это заявить? - person Vladimir; 22.12.2020
comment
Я только что обновил ответ, в который добавил импорт провайдера, уточнив, откуда он! Надеюсь, это поможет. - person Igor L Sambo LSambo; 23.12.2020
comment
Игорь, спасибо большое! Я просто забыл назвать импорт. - person Vladimir; 25.12.2020

Ладно, нашел как решить. Это довольно просто. Поэтому в 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 (значение: значение).

person Vladimir    schedule 22.12.2020