Flutter - auto_route _CustomNavigatorState error - flutter

I'm using the auto_route package in order to route my app and until 2 days ago everything worked just fine, until now.
For some reason, I'm getting the following error.
SYSTEM:
flutter: 1.22
dart: 2.10.0
auto_route: ^0.6.7
ERROR:
../../.pub-cache/hosted/pub.dartlang.org/custom_navigator-0.3.0/lib/custom_navigator.dart:60:7: Error: The non-abstract class '_CustomNavigatorState' is missing implementations for these members:
- WidgetsBindingObserver.didPushRouteInformation
Try to either
- provide an implementation,
- inherit an implementation from a superclass or mixin,
- mark the class as abstract, or
- provide a 'noSuchMethod' implementation.
class _CustomNavigatorState extends State<CustomNavigator>
^^^^^^^^^^^^^^^^^^^^^
/opt/flutter/packages/flutter/lib/src/widgets/binding.dart:122:16: Context: 'WidgetsBindingObserver.didPushRouteInformation' is defined here.
Future<bool> didPushRouteInformation(RouteInformation routeInformation) {
main.dart
import 'package:device_simulator/device_simulator.dart';
import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart';
import 'Test.gr.dart' as r;
void main() => runApp(MyApp());
const bool debugEnableDeviceSimulator = true;
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
builder: ExtendedNavigator.builder<r.Router>(
router: r.Router(),
builder: (context, extendedNav) => DeviceSimulator(
enable: debugEnableDeviceSimulator,
child:
Scaffold(body: extendedNav, backgroundColor: Colors.red ),
),
),
);
}
}
Also, I should mention, because of some update of flutter I guess, now I need to use r.Router instead of just Router.
Test.dart
import 'package:auto_route/auto_route_annotations.dart';
#MaterialAutoRouter(
routes: <AutoRoute>[],
)
class $Router {}
And also if has any importance here it's the generated file
Test.gr.dart
// GENERATED CODE - DO NOT MODIFY BY HAND
// **************************************************************************
// AutoRouteGenerator
// **************************************************************************
// ignore_for_file: public_member_api_docs
import 'package:auto_route/auto_route.dart';
class Routes {
static const all = <String>{};
}
class Router extends RouterBase {
#override
List<RouteDef> get routes => _routes;
final _routes = <RouteDef>[];
#override
Map<Type, AutoRouteFactory> get pagesMap => _pagesMap;
final _pagesMap = <Type, AutoRouteFactory>{};
}
Do you have any idea what's going one here or how can I fix this? Or if I can't fix this, what can I use instead of the auto_route package which will offer me the same benefice?

i solved this problem with add some function on library CustomNavigator :
Future<bool> didPushRouteInformation(RouteInformation routeInformation) {
return didPushRoute(routeInformation.location);
}

Find the place where the error is generating. Add the following function below there. I did the same and my problem is solved.
Future<bool> didPushRouteInformation(RouteInformation routeInformation) {
return didPushRoute(routeInformation.location);
}

Related

How do I mock a bloc in Flutter, with states being emitted in response to events from a widget under test

I'm trying to test a widget that makes use of a bloc. I'd like to be able to emit states from my mocked bloc in response to events being fired by the widget under test. I've tried a number of approaches without success. I'm not sure if I'm making some simple error or if I'm approaching the problem all wrong.
Here is a simplified project which demonstrates my issue. (the complete code for this can be found at https://github.com/andrewdixon1000/flutter_bloc_mocking_issue.git)
very simple bloc
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
class MyBloc extends Bloc<MyEvent, MyState> {
MyBloc() : super(FirstState());
#override
Stream<MyState> mapEventToState(
MyEvent event,
) async* {
if (event is TriggerStateChange) {
yield SecondState();
}
}
}
#immutable
abstract class MyEvent {}
class TriggerStateChange extends MyEvent {}
#immutable
abstract class MyState {}
class FirstState extends MyState {}
class SecondState extends MyState {}
My widget under test
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'bloc/my_bloc.dart';
import 'injection_container.dart';
class FirstPage extends StatefulWidget {
const FirstPage({Key? key}) : super(key: key);
#override
_FirsPageState createState() => _FirsPageState();
}
class _FirsPageState extends State<FirstPage> {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => serviceLocator<MyBloc>(),
child: Scaffold(
appBar: AppBar(title: Text("Page 1")),
body: Container(
child: BlocConsumer<MyBloc, MyState>(
listener: (context, state) {
if (state is SecondState) {
Navigator.pushNamed(context, "SECONDPAGE");
}
},
builder: (context, state) {
if (state is FirstState) {
return Column(
children: [
Text("State is FirstState"),
ElevatedButton(
onPressed: () {
BlocProvider.of<MyBloc>(context).add(TriggerStateChange());
},
child: Text("Change state")),
],
);
} else {
return Text("some other state");
}
},
),
),
),
);
}
}
my widget test
This is where I'm struggling. What I'm doing is loading the widget and then tapping the button. This causes the widget to add an event to the bloc. What I want to be able to do is have my mock bloc emit a state in response to this, such that the widget's BlocConsumer's listener will see the state change the navigate. As you can see from the comment in the code I've tried a few things without luck. Current nothing I've tried results in the listener seeing a state change.
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart' as mocktail;
import 'package:get_it/get_it.dart';
import 'package:test_bloc_issue/bloc/my_bloc.dart';
import 'package:test_bloc_issue/first_page.dart';
class MockMyBloc extends MockBloc<MyEvent, MyState> implements MyBloc {}
class FakeMyState extends Fake implements MyState {}
class FakeMyEvent extends Fake implements MyEvent {}
void main() {
MockMyBloc mockMyBloc;
mocktail.registerFallbackValue<MyState>(FakeMyState());
mocktail.registerFallbackValue<MyEvent>(FakeMyEvent());
mockMyBloc = MockMyBloc();
var nextScreenPlaceHolder = Container();
setUpAll(() async {
final di = GetIt.instance;
di.registerFactory<MyBloc>(() => mockMyBloc);
});
_loadScreen(WidgetTester tester) async {
mocktail.when(() => mockMyBloc.state).thenReturn(FirstState());
await tester.pumpWidget(
MaterialApp(
home: FirstPage(),
routes: <String, WidgetBuilder> {
'SECONDPAGE': (context) => nextScreenPlaceHolder
}
)
);
}
testWidgets('test', (WidgetTester tester) async {
await _loadScreen(tester);
await tester.tap(find.byType(ElevatedButton));
await tester.pump();
// What do I need to do here to mock the state change that would
// happen in the real bloc when a TriggerStateChange event is received,
// such that the listener in my BlocConsumer will see it?
// if tried:
// whenListen(mockMyBloc, Stream<MyState>.fromIterable([SecondState()]));
// and
// mocktail.when(() => mockMyBloc.state).thenReturn(SecondState());
await tester.pumpAndSettle();
expect(find.byWidget(nextScreenPlaceHolder), findsOneWidget);
});
}
I took a look and opened a pull request with my suggestions. I highly recommend thinking of your tests in terms of notifications and reactions. In this case, I recommend having one test to verify that when the button is tapped, the correct event is added to the bloc (the bloc is notified). Then I recommend having a separate test to ensure that when the state changes from FirstState to SecondState that the correct page is rendered (the UI reacts to state changes appropriately). In the future, I highly recommend taking a look at the example apps since most of them are fully tested.

Riverpod ProviderListener - 'StateNotifierProvider<Auth, bool>' can't be assigned to 'ProviderBase<Object, StateController<bool>>'

I'm trying to use a ProviderListener from Riverpod to listen to my authProvider and control the page displayed if a user is authorized or not. I'm getting the error:
error: The argument type 'StateNotifierProvider<Auth, bool>' can't be assigned to the parameter type 'ProviderBase<Object, StateController>'.
The error shows up on the: provider: authProvider, inside the ProviderListener
I'm wondering if it's due to the update on StateNotifierProvider?
I would like to know how to use the ProviderListener better even if there's a better way to handle the authorization flow (I'm VERY open to feedback and criticism and greatly appreciate any time a person can take to help). I cut out non-relevant code
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
class Auth extends StateNotifier<bool> {
Auth() : super(false);
void setAuth(bool auth) {
state = auth;
}
}
final authProvider = StateNotifierProvider<Auth, bool>((ref) => Auth());
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(
ProviderScope(
child: MyApp(),
),
);
}
class MyApp extends StatefulHookWidget {
// const MyApp({Key key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final Future<FirebaseApp> _fbMyApp = Firebase.initializeApp();
Widget route = SplashScreen();
#override
Widget build(BuildContext context) {
return ProviderListener<StateController<bool>>(
provider: authProvider,
onChange: (context, auth) {
if (auth.state = true) {
route = HomeScreen();
} else {
route = SplashScreen();
}
},
child: MaterialApp(
home: route,
);
}
}
I managed to get it to sort of work by changing to:
return ProviderListener<StateNotifier<bool>>(
provider: authProvider.notifier,
it's giving me a non-breaking error of:
info: The member 'state' can only be used within instance members of subclasses of 'package:state_notifier/state_notifier.dart'. (invalid_use_of_protected_member)
and not working properly - the state isn't being updated when I'm using a context.read
context.read(authProvider.notifier).state = true;
So it's buggy but not fully broken. At least it's some progress. I would still love help and any feedback anyone wants to give!
Remove StateController from ProviderListener, leave only the type (bool in this case)
return ProviderListener<bool>(
provider: authProvider, //this will read the state of your provider (a bool state)
onChange: (context, auth) {
if (auth) { //remove setter auth = true, it doesn't make sense to set a value inside an if
route = HomeScreen();
} else {
route = SplashScreen();
}
},
child: MaterialApp(
home: route,
);
This way you're reading the state of your StateNotifier

Can't pass in a subtype of an abstract class as argument into a function

I followed the BloC tutorial and did according to them, I have a Bloc which has this method
#override Stream<FridgeState> mapEventToState(FridgeEvent event) async* { .. }
where FridgeEvent is an abstract class
abstract class FridgeEvent {
const FridgeEvent();
}
class CreateFridgeEvent extends FridgeEvent {
final double lat, lng;
CreateFridgeEvent({required this.lat, required this.lng});
}
class DeleteFridgeEvent extends FridgeEvent {}
When instantiating the bloc within the widget and passing in a subclass of FridgeEvent, I get an error that the parameter type can't be assigned. What am I doing wrong?
Upon request, here's the code of the bloc
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:foodey_flutter/domain/fridge/entity/Fridge.dart';
import 'package:foodey_flutter/domain/fridge/usecase/CreateFridgeUseCase.dart';
import 'package:foodey_flutter/factory/FridgeFactory.dart';
import 'package:foodey_flutter/ui/fridge/create//event.dart';
import 'package:foodey_flutter/ui/fridge/create//state.dart';
class FridgeBloc extends Bloc<FridgeEvent, FridgeState> {
CreateFridgeUseCase? createFridgeUseCase;
FridgeBloc(FridgeState initialState) : super(initialState) {
FridgeFactory.inject(this);
}
#override
Stream<FridgeState> mapEventToState(FridgeEvent event) async* {
if (event is CreateFridgeEvent) {
Fridge? result = await createFridgeUseCase?.execute(event.lat, event.lng);
if (result != null)
yield FridgeSuccessfullyLoadedState(result);
else
yield FridgeErrorState(
exception: Exception("Failed to create a fridge"));
} else {
yield FridgeErrorState(exception: Exception("Operation not supported"));
}
}
}
Here's the code of the widget
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:foodey_flutter/ui/fridge/create/FridgeBloc.dart';
import 'package:foodey_flutter/ui/fridge/create/event.dart';
class CreateFridgeWidget extends StatefulWidget {
#override
_CreateFridgeState createState() => _CreateFridgeState();
}
class _CreateFridgeState extends State<CreateFridgeWidget> {
late FridgeBloc _bloc;
#override
void initState() {
super.initState();
this._bloc = BlocProvider.of<FridgeBloc>(context);
}
#override
Widget build(BuildContext context) {
return Container(
key: widget.key,
alignment: Alignment.center,
padding: EdgeInsets.all(8),
child: Column(
children: [
Text("Are you sure you want to create your fridge?"),
Row(
children: [
TextButton(
key: widget.key,
onPressed: () {
_bloc.add(CreateFridgeEvent(lat: 0, lng: 1));
},
child: Text("Yes"),
),
TextButton(
key: widget.key,
onPressed: () => {},
child: Text("No"),
),
],
)
],
),
);
}
}
And here are my defined events
abstract class FridgeEvent {
const FridgeEvent();
}
class CreateFridgeEvent extends FridgeEvent {
final double lat, lng;
CreateFridgeEvent({required this.lat, required this.lng});
}
class DeleteFridgeEvent extends FridgeEvent {}
Update: After copy pasting the code here and checking for completeness I found the issue. For anyone facing the same: the import of the event within the bloc code contains two /'s within the path. After removing one / the compiler didn't complain anymore that the wrong type is being passed. No idea how this second / was added and why the compiler didn't complain at all about that. Apparently, the objects defined in ..//event.dart are not the same when imported from ../event.dart
After copy pasting the code here and checking for completeness I found the issue. For anyone facing the same: the import of the event within the bloc code contains two /'s within the path. After removing one / the compiler didn't complain anymore that the wrong type is being passed. No idea how this second / was added and why the compiler didn't complain at all about that.
Apparently, the objects defined in ..//event.dart are not the same when imported from ../event.dart

Flutter GetIt Plugin - No type xxx is registered inside GetIt

I set everything up as shown in the example project:
import 'package:get_it/get_it.dart';
import 'package:places/services/authService.dart';
final locator = GetIt.instance;
void setupLocator() {
locator.registerSingleton<AuthService>(AuthService());
print("registered");
}
with the call in the main file
void main() {
setupLocator();
runApp(MyApp());
}
I have some Check where the locator also correctly return my AuthService
class AuthGuardView extends StatefulWidget {
AuthGuardView({Key key}) : super(key: key);
#override
_AuthGuardViewState createState() => _AuthGuardViewState();
}
class _AuthGuardViewState extends State<AuthGuardView> {
#override
Widget build(BuildContext context) {
return ViewModelProvider<AuthGuardViewModel>.withConsumer(
viewModel: AuthGuardViewModel(),
onModelReady: (model) => model.initialise(),
builder: (context, model, child) => model.isLoggedIn
? Container(
color: Colors.white,
child: Text("Logged In"),
)
: SignUpView(),
);
}
}
class AuthGuardViewModel extends ChangeNotifier {
AuthService _authService = locator<AuthService>();
bool isLoggedIn = false;
void initialise() async {
isLoggedIn = await _authService.isLoggedIn();
notifyListeners();
}
}
If I do the exact same thing inside the ViewModel for the SignUpView I get the following error
flutter: The following assertion was thrown building SignUpView(dirty, state: _SignUpViewState#01129):
flutter: No type AuthService is registered inside GetIt.
flutter: Did you forget to pass an instance name?
flutter: (Did you accidentally do GetIt sl=GetIt.instance(); instead of GetIt sl=GetIt.instance;did you
flutter: forget to register it?)
flutter: 'package:get_it/get_it_impl.dart':
flutter: Failed assertion: line 248 pos 14: 'instanceFactory != null'
In the ViewModel for the AuthGuard I do successfully retrieve the auth service. I also commented out the locator code (because I thought it might be the async call or something like that) but the same error persists.
I am using get_it: ^4.0.1 but the error persists when downgrading to 3.x.x
Here the SignUpViewModel
class SignUpViewModel extends ChangeNotifier {
SignUpViewModel(){
if(locator.isRegistered<AuthService>()) {
AuthService _authService = locator<AuthService>();
}
}
var textInputFormatter = [
WhitelistingTextInputFormatter(RegExp(r'\d')),
PhoneNumberTextInputFormatter()
];
var textEditingController;
var context;
}
This happens when the class to be registered as singleton has async methods. To fix this you need to await the singleton to be fully generated before runApp() is ran.
void main() async {
/* WidgetsFlutterBinding.ensureInitialized() is required in Flutter v1.9.4+
* before using any plugins if the code is executed before runApp.
*/
WidgetsFlutterBinding.ensureInitialized();
// Configure injecction
await setupLocator();
runApp(MyApp());
}
Adding this answer, as I think it might help others!
I have faced the same issue earlier. For me, it was due to an ordering issue. So make sure to initiate/declare the dependency objects first and then instantiate/declare the dependent one.
Using the latest get_it version in pubspec.yaml ( now it is get_it: ^4.0.2 ) resolve the issue for me.
I have also faced this issue. Nothing made sense. Then I remembered, that I recently did case sensitive renaming.
I changed i.e. Services/Database/Database.dart to services/database/database.dart, but in one file, I used import with the lowercased version, while in the other, it still was the uppercased version. Making the case consistent throughout the project was the fix I needed.

How to use same Future another .dart file? (Flutter)

I'd like to use Future teamMember() in another .dart file. Any contributions are welcome!
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'dart:async';
void main() {
runApp(new MaterialApp(
home: new Popups(),
));
}
class Popups extends StatefulWidget {
#override
_PopupsState createState() => _PopupsState();
}
class _PopupsState extends State<Popups> {
Future teamMember() async {
await showDialog(
context: context,
...
...
In the other class where you need it create a reference to your popup class and then call teamMember.
class OtherClass extends StatelessWidget {
Popups _popups = Popups();
.
.
RaisedButton(
child: Text("show team member popup"),
onPressed: (){
_popups.teamMember();
}
)
}
hope this helps