BlocProvider not finding Generic Flutter Bloc - flutter

I have the below generic Bloc which I am trying to use.
class ManageFeedbackBloc<T extends IContribute>
extends Bloc<ManageFeedbackEvent, ManageFeedbackState> {}
I used the below provider to create it:
void _navigateToContributionPage(
BuildContext context, FeedbackCategory category) {
final userFeedbackBloc = BlocProvider.of<UserFeedbacksBloc>(context);
final manageFeedbackBloc = ManageFeedbackBloc<DoctorFeedback>(
repository: ManageFeedbackRepository());
final manageFeedbackDestination =
BlocProvider<ManageFeedbackBloc<DoctorFeedback>>(
create: (context) => manageFeedbackBloc,
child: ManageFeedbackPage<DoctorFeedback>(),
lazy: false,
);
userFeedbackBloc.monitorFeedbackChanges(
BlocProvider.of<ManageFeedbackBloc<DoctorFeedback>>(context));
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => manageFeedbackDestination));
}
The above code crashes on this line:
userFeedbackBloc.monitorFeedbackChanges(
BlocProvider.of<ManageFeedbackBloc<DoctorFeedback>>(context));
I get the error:
Another exception was thrown: Error: Could not find the correct
Provider<ManageFeedbackBloc> above this
ContributionSelectorPage Widget
If I also try to skip the above and use BlocBuilder, I get the same error. Please see the code below:
class ManageFeedbackPage<T extends IContribute> extends StatelessWidget {
final _formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return BlocConsumer<ManageFeedbackBloc<T>, ManageFeedbackState>(
listenWhen: (_, currentState) => currentState.isListenerState,
buildWhen: (_, currentState) => !currentState.isListenerState,
listener: (context, state) async {
if (state is ManageFeedbackPromptUpdate) {
_promptActionAlert(
context,
'Modification',
'Êtes vous sûr de vouloir modifier ce retour d\'expérience ?',
ManageFeedbackUpdateFeedback());
return;
}
throw UnimplementedError(
'manage_feedback_page - UnImplemented State: Tried to implement listening state to ${state.toString()}');
},
builder: (context, state) {
if (state is ManageFeedbackLoading) {
return _getLoadingState();
}
return _getLoadedState(context);
},
);
}
}
Is it that generic bloc is not supported?
I am using flutter_bloc: ^7.0.0

So I believe I found the problem:
final manageFeedbackDestination =
BlocProvider<ManageFeedbackBloc<DoctorFeedback>>(
create: (context) => manageFeedbackBloc,
child: ManageFeedbackPage<DoctorFeedback>(),
lazy: false,
);
BlocProvider type should not specify the generic type just the type of the BloC without the generic.
final manageFeedbackDestination =
BlocProvider<ManageFeedbackBloc>(
create: (context) => manageFeedbackBloc,
child: ManageFeedbackPage<DoctorFeedback>(),
lazy: false,
);
This worked for me BlocProvider<ManageFeedbackBloc> instead of BlocProvider<ManageFeedbackBloc<DoctorFeedback>>.

Related

Flutter convert part of QuizesBloc state to QuizState

I have a List<Quiz> objects and I added these to QuizesState(Bloc is QuizesBloc) and displaying the listview. If I click on any single Quiz I want to add that particular Quiz object to another state called QuizState(Bloc is QuizBloc). How to do this?
(1) Add a selectedQuizId to your QuizesState.
(2) In your QuizesView (with the list of quizzes) add a BlocListener, listening to changes in QuizesState and if selectedQuizId is not null.
(3) navigate to a quiz route and provide selectedQuizId as an arugument
(4) create your QuizPage like this
class QuizPage extends StatelessWidget {
const QuizPage({super.key});
static Route<void> route(String? quizId) {
return MaterialPageRoute(
fullscreenDialog: true,
builder: (context) => BlocProvider(
create: (context)
{
return QuizBloc(
quizId: quizId,
repository: context.read<QuizRepository>()
)..add(QuizRequested());
},
child: const QuizPage(),
),
);
}
#override
Widget build(BuildContext context) {
return const QuizView();
}
}
(5) pick up the quizId upon initializing the QuizBloc

How to pass in arguments in flutter when user input URL address (using auto_route)

My app uses flutter with auto_router 0.6.9 package, and I want user to be able to type in a web address http://localhost/#/book/123 and be able to access book 123 and display book123, their name, and email if they are logged in. I had figured out the routing part with auto_router but when the user put the address directly into the browser, the arguments should be passed into the page are null.
I was wondering is there a way to parse the url and pass in the arguments required before displaying the page. Thanks for any help or hints!
My router.dart page:
#MaterialAutoRouter(
routes: <AutoRoute>[
// some other pages...
MaterialRoute(path: "/book/:id", page: MultiProvider),
],
)
class $ModularRouter {}
The router.gr.dart generated for multiprovider arguments class is:
/// MultiProvider arguments holder class
class MultiProviderArguments {
final Key key;
final List<SingleChildWidget> providers;
final Widget child;
final Widget Function(BuildContext, Widget) builder;
MultiProviderArguments(
{this.key, #required this.providers, this.child, this.builder});
}
My book menu page contains a navigation:
(this is an example of how I normally call the BookPage in another page)
child: GestureDetector(
onTap: () => ExtendedNavigator.of(context).push(
Routes.multiProvider(id: book[index].bookID),
arguments: MultiProviderArguments(
providers: [
Provider<UserData>.value(
value: userdata,
),
Provider<List<BookInfo>>.value(
value: book,
),
],
child: BookPage(
bookId: book[index].bookID,
name: userdata.userName,
email: userdata.email,
),
),
),
...
And BookPage takes in 3 arguments bookId, name and email:
class BookPage extends StatefulWidget {
final String bookId;
final String name;
final String email;
BookPage({
this.bookId,
this.name,
this.email,
});
...
}
...
class _BookPageState extends State<BookPage> {
...
Widget build(BuildContext context) {
final currentUser = Provider.of<UserData>(context, listen: false);
final currentBook = Provider.of<List<BookInfo>>(context, listen: false);
...
}
}
I had figured out a Band-Aid solution (which I don't consider to be permanent):
I had abandoned using auto-route which I have no idea how to parse the url and use onGenerateRoute instead:
onGenerateRoute: (settings) {
final settingsUri = Uri.parse(settings.name);
final user = Provider.of<User>(context);
if (user == null) {
return MaterialPageRoute(
builder: (context) {
return LogInPage();
}
);
} else {
// Handle '/'
if (settingsUri.pathSegments.length == 0) {
return MaterialPageRoute(
builder: (context) {
return PageWrapper(false, bookid);
}
);
}
// Handle '/book/:id'
if (settingsUri.pathSegments.length == 2) {
if (settingsUri.pathSegments.first != 'book') {
return MaterialPageRoute(
builder: (context) {
return UnknownPage();
}
);
}
final bookid = settingsUri.pathSegments.elementAt(1);
if (bookid == null) {
return MaterialPageRoute(
builder: (context) {
return UnknownPage();
}
);
}
return MaterialPageRoute(
builder: (context) {
return PageWrapper(false, bookid);
}
);
}
}
//Handle other unknown Routes
return MaterialPageRoute(
builder: (context) {
return UnknownPage();
}
);
}
The PageWrapper authenticates user, build providers, and call BookPage class, I put the code in a place where userData providers had already been initialized:
if (this.needRedirect == true) {
return Scaffold(
...
body: FutureBuilder(
future: Future.wait([userData]),
builder: (context, snapshot) {
return MultiProvider(
providers: [
//initialize using streamproviders
...
],
child: BookPage(
bookId: book[index].bookID,
name: userdata.userName,
email: userdata.email,
),
);
}),
);
}
else {...}
The short answer is I had called the WrapperPage in onGenerateRoutes because the providers are not initialized in main.dart, but they are initialized in WrapperPage. Then I put the redirect code after provider initialization in WrapperPage and called BookPage.
This method had caused other bugs, but it had achieved its purpose.

Initial Route if user is logged Flutter

I have created a Future to know if the user is logged, but the initial route isn't save. Then I recive this route in the Initial Route of my material app.
void main() async{
WidgetsFlutterBinding.ensureInitialized();
await UserProvider().isUserLoggedIn();
runApp(MiRoulotte());
}
class MiRoulotte extends StatelessWidget {
final _userProvider = UserProvider();
...
initialRoute: _userProvider.initialRoute,
routes: {
'InitialPage': (BuildContext context) => InitialPage(),
'SignIn': (BuildContext context) => SignInPage(),
'SignUp': (BuildContext context) => SignUpPage(),
'EditProfile': (BuildContext context) => EditProfilePage()
},
)
);
}
}
Future isUserLoggedIn() async{
var user = await _firebaseAuth.currentUser();
if(user != null){
try{
this._currentUser = await getUser(user.uid);
this._initialRoute = 'InitialPage';
}catch(error){
this._initialRoute = 'SignIn';
}
} else{
this._initialRoute = 'SignIn';
}
}
}
photo
You are creating two different instances of UserProvider, that's the problem. You assign the _initialRoute on the first one, but then create a second one which i't assigned.
If you are using Provider, you should use the same instance for that tree and then get it retrieve it through a Consumer for example. Replace your main with:
void main() async{
WidgetsFlutterBinding.ensureInitialized();
UserProvider userProvider = UserProvider();
await userProvider.isUserLoggedIn();
runApp(
Provider<UserProvider>(
create: (_) => userProvider,
builder: (BuildContext context, Widget child) => child,
child: MiRoulotte()),
),
);
}
And then your MiRoulotte fetch it in your build method using a Consumer widget or explicit variable assign:
UserProvider _userProvider = Provider.of<UserProvider>(context, listen: false);
This way you ensure that you are using always the same instance.

Declarative auth routing with Firebase

Rather than pushing the user around with Navigator.push when they sign in or out, I've been using a stream to listen for sign in and sign out events.
StreamProvider<FirebaseUser>.value(
value: FirebaseAuth.instance.onAuthStateChanged,
)
It works great for the home route as it handles logging in users immediately if they're still authed.
Consumer<FirebaseUser>(
builder: (_, user, __) {
final isLoggedIn = user != null;
return MaterialApp(
home: isLoggedIn ? HomePage() : AuthPage(),
// ...
);
},
);
However, that's just for the home route. For example, if the user then navigates to a settings page where they click a button to sign out, there's no programmatic logging out and kicking to the auth screen again. I either have to say Navigator.of(context).pushNamedAndRemoveUntil('/auth', (_) => false) or get an error about user being null.
This makes sense. I'm just looking for possibly another way that when they do get logged out I don't have to do any stack management myself.
I got close by adding the builder property to the MaterialApp
builder: (_, widget) {
return isLoggedIn ? widget : AuthPage();
},
This successfully moved me to the auth page after I was unauthenticated but as it turns out, widget is actually the Navigator. And that means when I went back to AuthPage I couldn't call anything that relied on a parent Navigator.
What about this,you wrap all your screens that depend on this stream with this widget which hides from you the logic of listening to the stream and updating accordingly(you should provide the stream as you did in your question):
class AuthDependentWidget extends StatelessWidget {
final Widget childWidget;
const AuthDependentWidget({Key key, #required this.childWidget})
: super(key: key);
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {//you handle other cases...
if (snapshot.currentUser() != null) return childWidget();
} else {
return AuthScreen();
}
},
);
}
}
And then you can use it when pushing from other pages as follows:
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (ctx) => AuthDependentWidget(
childWidget: SettingsScreen(),//or any other screen that should listen to the stream
)));
I found a way to accomplish this (LoVe's great answer is still completely valid) in case anyone else steps on this issue:
You'll need to take advantage of nested navigators. The Root will be the inner navigator and the outer navigator is created by MaterialApp:
return MaterialApp(
home: isLoggedIn ? Root() : AuthPage(),
routes: {
Root.routeName: (_) => Root(),
AuthPage.routeName: (_) => AuthPage(),
},
);
Your Root will hold the navigation for an authed user
class Root extends StatefulWidget {
static const String routeName = '/root';
#override
_RootState createState() => _RootState();
}
class _RootState extends State<Root> {
final _appNavigatorKey = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
final canPop = _appNavigatorKey.currentState.canPop();
if (canPop) {
await _appNavigatorKey.currentState.maybePop();
}
return !canPop;
},
child: Navigator(
initialRoute: HomePage.routeName,
onGenerateRoute: (RouteSettings routeSettings) {
return MaterialPageRoute(builder: (_) {
switch (routeSettings.name) {
case HomePage.routeName:
return HomePage();
case AboutPage.routeName:
return AboutPage();
case TermsOfUsePage.routeName:
return TermsOfUsePage();
case SettingsPage.routeName:
return SettingsPage();
case EditorPage.routeName:
return EditorPage();
default:
throw 'Unknown route ${routeSettings.name}';
}
});
},
),
);
}
}
Now you can unauthenticate (FirebaseAuth.instance.signout()) inside of the settings page (or any other page) and immediately get kicked out to the auth page without calling a Navigator method.

Routing error shows missing one argument?

This is how i am routing to next page ,
'/ot1': (context) => CustomListView(),
and it was working fine onTAP
onTap: (){
Navigator.pushNamed(context, '/ot1');
},
but when i created constructor in class CustomListView and passed field i got this error that one argument is missing in this line '/ot1': (context) => CustomListView(),
this is the code of my class CustomListView share below
class CustomListView extends StatelessWidget {
final List<Spacecraft> spacecrafts;
CustomListView(this.spacecrafts);
Widget build(context) {
return ListView.builder(
itemCount: spacecrafts.length,
itemBuilder: (context, int currentIndex) {
return createViewItem(spacecrafts[currentIndex], context);
},
);
}
I have searched for it for so much and didn't find a solution new to programming and FLUTTER language please HELP
If you want to pass data between screen using pushedName do it like,
Navigator.pushNamed(
context, '/ot1', arguments: mFeedData);
and fetch data like,
#override
Widget build(BuildContext context) {
mFeedData = ModalRoute.of(context).settings.arguments;
....
}
if you do not wish to pass any data then remove below portion from your code
CustomListView(this.spacecrafts)
or make it as an optional positional argument,
CustomListView([this.spacecrafts])
What is Positional parameter?
Wrapping a set of function parameters in [] marks them as optional positional parameters:
What is Named parameters?
When calling a function, you can specify named parameters using paramName: value.
For example:
enableFlags(bold: true, hidden: false);
When defining a function, use {param1, param2, …} to specify named parameters:
/// Sets the [bold] and [hidden] flags ...
void enableFlags({bool bold, bool hidden}) {...}
Try adding square brackets [this.spacecrafts] so that the spacecraft argument becomes an optional argument.
class CustomListView extends StatelessWidget {
final List<Spacecraft> spacecrafts;
CustomListView([this.spacecrafts]); # <- this is where you use the brackets
...
I am using this solution for navigation in my Flutter apps:
class RouteGenerator {
static Route<dynamic> generateRoute(RouteSettings settings) {
// Getting arguments passed in while calling Navigator.pushNamed
final args = settings.arguments;
switch (settings.name) {
case '/':
return MaterialPageRoute(builder: (_) => Home());
case '/routeWithoutArguments':
return MaterialPageRoute(
builder: (_) => PageWithoutArguments(),
);
case '/routeWithArguments':
if (args is String) { // I check if the arguments provided are valids
return MaterialPageRoute(
builder: (_) => PageWithArguments(myString: args),
);
}
return _errorRoute();
default:
return _errorRoute();
}
}
static Route<dynamic> _errorRoute() {
return MaterialPageRoute(builder: (_) {
return Scaffold(
appBar: AppBar(
title: Text('Error'),
),
body: Center(
child: Text('ERROR'),
),
);
});
}
}
Then in my widget App:
MaterialApp(
initialRoute: '/',
onGenerateRoute: RouteGenerator.generateRoute,
...
)
Then to call my routes without arguments:
Navigator.pushNamed(
context,
'/routeWithoutArguments',
);
and to call with arguments:
Navigator.pushNamed(
context,
'/routeWithArguments',
arguments: "my argument",
);
I have learn this solution from ResoCoder