How to implement widget tests by using MockBloc? - flutter

I'm trying to implement a Widget Test in order to test a login form. This test depends on a bloc which I'm mocking by using MockBloc. However, it throws the following error:
══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK╞════════════════════════════════════════════════════
The following StateError was thrown running a test:
Bad state: No method stub was called from within `when()`. Was a real method called, or perhaps an
extension method?
I found a similar error in the following link, but I do not see how that can help me to solve my problem.
I also looked at the following file on gitlub, which is an example of a widget test by using bloc_test. The link can be found on the official website of the Bloc Library - specifically in Todos App in Flutter using the Bloc library.
However, that example is using bloc_test: ^3.0.1 while I'm using bloc_test: ^8.0.0, which can be found here.
Here is a minimal example:
LoginForm Widget
class LoginForm extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Form(
key: '_loginForm',
child: Column(
children: <Widget>[
...
BlocConsumer<AuthenticationBloc, AuthenticationState>(
listener: (context, state) {
...
},
builder: (context, state) {
if (state is AuthenticationInitial) {
...
} else if (state is LoggingIn || state is LoggedIn) {
...
} else if (state is Error) {
return Column(
children: <Widget>[
...
Message(
message: state.message,
messageContainerWidth: 290,
messageContainerHeight: 51,
),
...
],
);
}
}
),
],
),
);
}
}
Message Widget
class Message extends StatelessWidget {
final String message;
final double messageContainerWidth;
final double messageContainerHeight;
...
#override
Widget build(BuildContext context) {
return Container(
width: messageContainerWidth,
height: messageContainerHeight,
child: Center(
child: message != ""
? Text(
message,
textAlign: TextAlign.center,
style: TextStyle(
color: Color.fromRGBO(242, 241, 240, 1),
fontSize: 15,
),
)
: child,
),
);
}
}
Widget Test (I want to test that a Message is shown when the Authentication state is Error)
...
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
...
// Mocking my LoginUser usecase
class MockLoginUser extends Mock implements LoginUser {}
// Mocking my bloc
class MockAuthenticationBloc
extends MockBloc<AuthenticationEvent, AuthenticationState>
implements AuthenticationBloc {}
class AuthenticationStateFake extends Fake implements AuthenticationState {}
void main() {
MockLoginUser mockLoginUser;
setUpAll(() {
registerFallbackValue<AuthenticationState>(AuthenticationStateFake());
});
setUp(() {
mockLoginUser = MockLoginUser();
authenticationBloc = AuthenticationBloc(loginUser: mockLoginUser);
});
group('Login', () {
testWidgets(
'should show a Message when the Authentication state is Error',
(WidgetTester tester) async {
whenListen(
authenticationBloc,
Stream.fromIterable(
[
LoggingIn(),
Error(
message: 'Some error message',
),
],
),
initialState: AuthenticationInitial(),
);
final widget = LoginForm();
await tester.pumpWidget(
BlocProvider<AuthenticationBloc>(
create: (context) => authenticationBloc,
child: MaterialApp(
title: 'Widget Test',
home: Scaffold(body: widget),
),
),
);
await tester.pumpAndSettle();
final messageWidget = find.byType(Message);
expect(messageWidget, findsOneWidget);
});
});
}
I will really appreciate it if someone can help me to solve the error, or can let me know another way to implement the widget tests.
Thanks in advance!

I solved the problem, I would like to share the answer, in case someone finds out the same problem.
First of all, this link was really helpful.
The solution was to change the Widget Test in the following way:
...
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
...
class MockAuthenticationBloc
extends MockBloc<AuthenticationEvent, AuthenticationState>
implements AuthenticationBloc {}
class AuthenticationStateFake extends Fake implements AuthenticationState {}
class AuthenticationEventFake extends Fake implements AuthenticationEvent {}
void main() {
group('Login', () {
setUpAll(() {
registerFallbackValue<AuthenticationState>(AuthenticationStateFake());
registerFallbackValue<AuthenticationEvent>(AuthenticationEventFake());
});
testWidgets(
'should show a Message when the Authentication state is Error',
(WidgetTester tester) async {
// arrange
final mockAuthenticationBloc = MockAuthenticationBloc();
when(() => mockAuthenticationBloc.state).thenReturn(
LoggingIn(), // the desired state
);
// find
final widget = LoginForm();
final messageWidget = find.byType(Message);
// test
await tester.pumpWidget(
BlocProvider<AuthenticationBloc>(
create: (context) => mockAuthenticationBloc,
child: MaterialApp(
title: 'Widget Test',
home: Scaffold(body: widget),
),
),
);
await tester.pumpAndSettle();
// expect
expect(messageWidget, findsOneWidget);
});
});
}

Related

How to implement widget tests by using mock cubit?

I tried to follow the answer to this question, but I was not able to make it work.
I reproduced my issue on the counter app, changing it as follow.
main.dart
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (BuildContext ctx) => DummyCubit(),
),
],
child: MaterialApp(
...
}
class _MyHomePageState extends State<MyHomePage> {
...
#override
Widget build(BuildContext context) {
return Scaffold(
...
body: Center(
child: CounterViewer(counter: _counter),
),
...
);
}
}
class CounterViewer extends StatelessWidget {
const CounterViewer({required this.counter, Key? key}) : super(key: key);
final int counter;
#override
Widget build(BuildContext context) {
return BlocBuilder<DummyCubit, AState>(
builder: (ctx, state) => (state is! StateLoaded)
? const CircularProgressIndicator()
: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
Text(
'$counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
);
}
}
dummy_cubit.dart
import 'package:bloc/bloc.dart';
import 'package:meta/meta.dart';
class DummyCubit extends Cubit<AState> {
DummyCubit() : super(const InitState());
Future<void> executeLogic() async {
emit(const StateLoading());
// do some logic
emit(StateLoaded('some data'));
}
}
#immutable
abstract class AState {
const AState();
}
class InitState extends AState {
const InitState();
}
class StateLoading extends AState {
const StateLoading();
}
class StateLoaded extends AState {
const StateLoaded(this.data);
final String data;
#override
String toString() => data.toString();
#override
bool operator ==(Object other) =>
identical(this, other) ||
(other is StateLoaded &&
runtimeType == other.runtimeType &&
data == other.data);
#override
int get hashCode => data.hashCode;
}
widget_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:bloc_test/bloc_test.dart';
import 'package:mocktail/mocktail.dart' as mocktail;
import 'package:counter/dummy_cubit.dart';
import 'package:counter/main.dart';
class MockDummyCubit extends MockCubit<AState> implements DummyCubit {}
class AStateFake extends Fake implements AState {}
final dummyCubit = MockDummyCubit();
Widget get counter => MultiBlocProvider(
providers: [
BlocProvider<DummyCubit>(
create: (BuildContext ctx) => dummyCubit,
),
],
child: const MaterialApp(
home: CounterViewer(counter: 1),
),
);
void main() {
setUpAll(() {
mocktail.registerFallbackValue(AStateFake());
});
group('Counter viewer', () {
mocktail.when(() => dummyCubit.state).thenReturn(InitState());
testWidgets('should build', (WidgetTester tester) async {
await tester.pumpWidget(counter);
});
});
}
When running the test, I get this error:
The following StateError was thrown running a test:
Bad state: No method stub was called from within `when()`. Was a real method called, or perhaps an
extension method?
And removing the mocktail.when line, I get this error:
The following _TypeError was thrown building CounterViewer:
type 'Null' is not a subtype of type 'AState'
How do I solve this issue?
How do I control which state is emitted by my DummyCubit?
After reading this, I found the solution
class MockDummyCubit extends MockCubit<AState> implements DummyCubit {}
class AStateFake extends Fake implements AState {}
void main() {
late MockDummyCubit dummyCubit;
setUpAll(() {
mocktail.registerFallbackValue(AStateFake());
});
setUp(() {
dummyCubit = MockDummyCubit();
mocktail.when(() => dummyCubit.state).thenReturn(const InitState());
});
group('Counter viewer', () {
testWidgets('should build', (WidgetTester tester) async {
await tester.pumpWidget(getCounter(dummyCubit));
});
});
}
Widget getCounter(MockDummyCubit dummyCubit) => MultiBlocProvider(
providers: [
BlocProvider<DummyCubit>(
create: (BuildContext ctx) => dummyCubit,
),
],
child: const MaterialApp(
home: CounterViewer(counter: 1),
),
);

Cannot retrieve the status of the internet connection when the app is re-launched or hot restarted with internet off

I am trying to use the BLoC library with the connectivity_plus plugin in Flutter. I have followed this post and this post to set up an Internet Cubit for the project. The code works fine when the app is started with the internet connection turned on.
However, with the internet connection turned off, if I kill the app and re-launch it or do a hot restart, the CircularProgressIndicator() is shown instead of Text("Internet Disconnected").
Turning the internet back on correctly shows Text("Internet Connected") widget. After this, If I turn off the internet connection again, this time around it correctly shows the Text("Internet Disconnected") widget.
Also, emitInternetDisconnected is not caught as an exception in the try catch block to update the app's state.
The problem with the CircularProgressIndicator() being always displayed with the internet disconnected occurs only when the app is re-launched or hot restarted. I cannot figure out the bug in my code. What should I do to fix my code? Thanks
This is the code in the internet_cubit.dart file
import 'dart:async';
import 'dart:io';
import 'package:bloc/bloc.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'internet_enum.dart';
part 'internet_state.dart';
class InternetCubit extends Cubit<InternetState> {
final Connectivity? connectivity;
StreamSubscription? connectivityStreamSubscription;
InternetCubit({required this.connectivity}) : super(InternetLoading()) {
monitorInternetConnection();
}
void monitorInternetConnection() async {
connectivityStreamSubscription =
connectivity!.onConnectivityChanged.listen((connectivityResult) async {
try {
final result = await InternetAddress.lookup("example.com");
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
if (connectivityResult == ConnectivityResult.wifi) {
emitInternetConnected(ConnectionType.WiFi);
} else if (connectivityResult == ConnectivityResult.mobile) {
emitInternetConnected(ConnectionType.Mobile);
} else if (connectivityResult == ConnectivityResult.none) {
emitInternetDisconnected();
}
}
} on SocketException catch (_) {
emitInternetDisconnected();
}
});
}
void emitInternetConnected(ConnectionType _connectionType) =>
emit(InternetConnected(connectionType: _connectionType));
void emitInternetDisconnected() => emit(InternetDisconnected());
#override
Future<void> close() async {
connectivityStreamSubscription!.cancel();
return super.close();
}
}
This is the code in the internet_state.dart file
part of 'internet_cubit.dart';
abstract class InternetState {}
class InternetLoading extends InternetState {}
class InternetConnected extends InternetState {
final ConnectionType? connectionType;
InternetConnected({required this.connectionType});
}
class InternetDisconnected extends InternetState {}
This is the code in my main.dart file
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'internet_cubit.dart';
void main() {
runApp(
BlocProvider<InternetCubit>(
create: (_) => InternetCubit(connectivity: Connectivity()),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter BLoC Demo',
home: SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text("Flutter BLoC Demo"),
centerTitle: true,
backgroundColor: Colors.blue[900],
),
body: Center(
child: Builder(
builder: (context) {
return MaterialButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => ScreenOne()));
},
color: Colors.black,
textColor: Colors.white,
child: Text("Screen 1"));
},
),
),
),
),
);
}
}
class ScreenOne extends StatelessWidget {
const ScreenOne({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text("Flutter BLoC Demo"),
centerTitle: true,
backgroundColor: Colors.blue[900],
),
body: Center(
child: BlocBuilder<InternetCubit, InternetState>(
builder: (_, state) {
if (state is InternetDisconnected) {
return Text("Internet disconnected");
} else if (state is InternetConnected) {
return Text("Internet connected");
}
return CircularProgressIndicator();
},
),
),
),
);
}
}
I suspected the problem to lie with my Internet connection at the network layer, so I tried a different approach. I will post my solution here so that it can be useful to someone with a similar problem.
First, I modified the internet_cubit.dart and internet_state.dart files like so:
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'internet_enum.dart';
part 'internet_state.dart';
class InternetCubit extends Cubit<InternetConnectionTypeState> {
final Connectivity? connectivity;
// ignore: cancel_subscriptions
StreamSubscription? internetConnectionTypeStreamSubscription;
InternetCubit({required this.connectivity}) : super(InternetConnectionTypeLoading()) {
monitorConnectionType();
}
void monitorConnectionType() async {
internetConnectionTypeStreamSubscription =
connectivity!.onConnectivityChanged.listen((connectivityResult) async {
if (connectivityResult == ConnectivityResult.wifi) {
emitConnectionType(ConnectionType.WiFi);
} else if (connectivityResult == ConnectivityResult.mobile) {
emitConnectionType(ConnectionType.Mobile);
}
});
}
void emitConnectionType(ConnectionType _connectionType) =>
emit(InternetConnectionType(connectionType: _connectionType));
#override
Future<void> close() async {
internetConnectionTypeStreamSubscription!.cancel();
return super.close();
}
}
part of 'internet_cubit.dart';
abstract class InternetConnectionTypeState {}
class InternetConnectionTypeLoading extends InternetConnectionTypeState {}
class InternetConnectionType extends InternetConnectionTypeState {
final ConnectionType? connectionType;
InternetConnectionType({required this.connectionType});
}
This is the internet_enum.dart file:
enum ConnectionType {
WiFi, Mobile
}
Next, I imported the internet_connection_checker package. I created a new cubit class called ConnectionCheckerCubit. Following the above code as a guide and the documentation for the internet_connection_checker package, here is the code for the connection_cubit.dart and the connection_state.dart file.
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:internet_connection_checker/internet_connection_checker.dart';
part 'connection_state.dart';
class ConnectionCheckerCubit extends Cubit<ConnectionCheckerState> {
final InternetConnectionChecker? internetConnectionChecker;
ConnectionCheckerCubit({required this.internetConnectionChecker}) : super(InternetConnectionLoading()) {
monitorInternetConnection();
}
// ignore: cancel_subscriptions
StreamSubscription? internetConnectionStreamSubscription;
void monitorInternetConnection() async {
internetConnectionStreamSubscription = InternetConnectionChecker().onStatusChange.listen((status) {
switch (status) {
case InternetConnectionStatus.connected:
emitInternetConnectionConnected(InternetConnectionStatus.connected);
break;
case InternetConnectionStatus.disconnected:
emitInternetConnectionDisconnected();
break;
}
});
}
void emitInternetConnectionConnected(InternetConnectionStatus _internetConnectionStatus) =>
emit(InternetConnectionConnected(internetConnectionStatus: _internetConnectionStatus));
void emitInternetConnectionDisconnected() => emit(InternetConnectionDisconnected());
#override
Future<void> close() async {
internetConnectionStreamSubscription!.cancel();
return super.close();
}
}
part of 'connection_cubit.dart';
abstract class ConnectionCheckerState {}
class InternetConnectionLoading extends ConnectionCheckerState {}
class InternetConnectionConnected extends ConnectionCheckerState {
final InternetConnectionStatus? internetConnectionStatus;
InternetConnectionConnected({required this.internetConnectionStatus});
}
class InternetConnectionDisconnected extends ConnectionCheckerState {}
In the main.dart file, I used a MultiBlocProvider in the main() function as a wrapper for runApp. In the ScreenOne widget, I used a BlocBuilder widget for the InternetCubit and used context.watch<ConnectionCheckerCubit>().state to monitor the state of the ConnectionCheckerCubit. Also, I have added a AppBlocObserver class for debugging purposes. Here is the code for the main.dart file:
import 'dart:developer';
import 'package:internet_connection_checker/internet_connection_checker.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:bloc/bloc.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter_bloc_api/internet_enum.dart';
import 'connection_cubit.dart';
import 'internet_cubit.dart';
void main() {
Bloc.observer = AppBlocObserver();
runApp(
MultiBlocProvider(
providers: [
BlocProvider<ConnectionCheckerCubit>(
create: (_) => ConnectionCheckerCubit(internetConnectionChecker: InternetConnectionChecker()),
),
BlocProvider<InternetCubit>(
create: (_) => InternetCubit(connectivity: Connectivity()),
),
],
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter BLoC Demo',
home: SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text("Flutter BLoC Demo"),
centerTitle: true,
backgroundColor: Colors.blue[900],
),
body: Center(
child: Builder(
builder: (context) {
return MaterialButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => ScreenOne()));
},
color: Colors.black,
textColor: Colors.white,
child: Text("Screen 1"));
},
),
),
),
),
);
}
}
class ScreenOne extends StatelessWidget {
const ScreenOne({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
appBar: AppBar(
title: Text("Flutter BLoC Demo"),
centerTitle: true,
backgroundColor: Colors.blue[900],
),
body: Center(
child: Builder(
builder: (context) {
final connectionState = context.watch<ConnectionCheckerCubit>().state;
final internetTypeState = context.watch<InternetCubit>().state;
if (connectionState is InternetConnectionDisconnected)
return Text("Internet Disconnected");
else if (connectionState is InternetConnectionConnected){
if (internetTypeState is InternetConnectionType && internetTypeState.connectionType == ConnectionType.WiFi)
return Text("WiFi");
else
return Text("Mobile");
}
return CircularProgressIndicator();
}
)
),
),
);
}
}
class AppBlocObserver extends BlocObserver {
#override
void onChange(BlocBase bloc, Change change) {
super.onChange(bloc, change);
log('onChange: ${bloc.runtimeType}, ${bloc.state} \nCurrent state: ${change.currentState}\nNext state: ${change.nextState}');
}
#override
void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
log('onError(${bloc.runtimeType}, ${bloc.state}, $error, $stackTrace)');
super.onError(bloc, error, stackTrace);
}
#override
void onEvent(Bloc bloc, Object? event) {
log('onEvent(${bloc.state}, ${bloc.runtimeType}, $event)');
super.onEvent(bloc, event);
}
#override
void onTransition(Bloc bloc, Transition transition) {
log('onTransition(${bloc.state}, ${bloc.runtimeType}, ${transition.currentState}, ${transition.nextState})');
super.onTransition(bloc, transition);
}
#override
void onCreate(BlocBase bloc) {
log('onCreate(${bloc.state}, ${bloc.runtimeType})');
super.onCreate(bloc);
}
#override
void onClose(BlocBase bloc) {
log('onTransition(${bloc.state}, ${bloc.runtimeType})');
super.onClose(bloc);
}
}
Here is a Github link for more information.
add this library connectivity: ^3.0.6
and try this code in your class:-
_startNetworkTesting(BuildContext context) async {
var result = await (Connectivity().checkConnectivity());
if (result == ConnectivityResult.none) {
setState(() {
//perform your action
});
} else if (result == ConnectivityResult.mobile) {
setState(() {
//perform your action
});
} else if (result == ConnectivityResult.wifi) {
setState(() {
//perform your action
});
}
}

how could i check connectivity using cubit?

I need to check the connectivity in every page inside my application using connectivity library,
So i will use a cubit inside the provider. the question is when to close the stream to make it possible to dispose it when the user close the app?
just like this:
import 'package:connectivity/connectivity.dart';
#override
dispose() {
super.dispose();
subscription.cancel();
}
1. Make sure you have imported flutter_bloc and connectivity_plus in your pubspec.yaml.
2. Create an InternetCubit files:
internet_cubit.dart
internet_state.dart
3. internet_state.dart:
Here we create enum with connection types for our cubit and cubit states:
part of 'internet_cubit.dart';
enum ConnectionType {
wifi,
mobile,
}
#immutable
abstract class InternetState {}
class InternetLoading extends InternetState {}
class InternetConnected extends InternetState {
final ConnectionType connectionType;
InternetConnected({#required this.connectionType});
}
class InternetDisconnected extends InternetState {}
4. internet_cubit.dart:
Cubit depends on connectivity plugin, so we import it and create a stream subscription to be able to react on connection changes.
Also we define two methods emitInternetConnected and emitInternetDisconnected that will change actual cubit state.
Make sure to dispose of stream subscription properly.
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:meta/meta.dart';
part 'internet_state.dart';
class InternetCubit extends Cubit<InternetState> {
final Connectivity connectivity;
StreamSubscription connectivityStreamSubscription;
InternetCubit({#required this.connectivity})
: assert(connectivity != null),
super(InternetLoading()) {
connectivityStreamSubscription =
connectivity.onConnectivityChanged.listen((connectivityResult) {
if (connectivityResult == ConnectivityResult.wifi) {
emitInternetConnected(ConnectionType.wifi);
} else if (connectivityResult == ConnectivityResult.mobile) {
emitInternetConnected(ConnectionType.mobile);
} else if (connectivityResult == ConnectivityResult.none) {
emitInternetDisconnected();
}
});
}
void emitInternetConnected(ConnectionType _connectionType) =>
emit(InternetConnected(connectionType: _connectionType));
void emitInternetDisconnected() => emit(InternetDisconnected());
#override
Future<void> close() {
connectivityStreamSubscription.cancel();
return super.close();
}
}
5. In your app main file create an instance of Connectivity plugin and pass it to your BlocProvider. Set up bloc consuming with your needs:
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
import 'package:flutter_application_4/cubit/internet_cubit.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
void main() => runApp(MyApp(connectivity: Connectivity()));
class MyApp extends StatelessWidget {
final Connectivity connectivity;
const MyApp({Key key, this.connectivity}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => InternetCubit(connectivity: connectivity),
child: MaterialApp(
title: 'Connectivity cubit',
home: Scaffold(
appBar: AppBar(
title: Text('Connectivity cubit spotlight'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
BlocBuilder<InternetCubit, InternetState>(
builder: (context, state) {
if (state is InternetConnected &&
state.connectionType == ConnectionType.wifi) {
return Text(
'Wifi',
style: TextStyle(color: Colors.green, fontSize: 30),
);
} else if (state is InternetConnected &&
state.connectionType == ConnectionType.mobile) {
return Text(
'Mobile',
style: TextStyle(color: Colors.yellow, fontSize: 30),
);
} else if (state is InternetDisconnected) {
return Text(
'Disconnected',
style: TextStyle(color: Colors.red, fontSize: 30),
);
}
return CircularProgressIndicator();
},
),
],
),
),
),
),
);
}
}

Flutter - trigger navigation when Provider variable changes

I'm trying to show a splash screen on initial app startup until I have all of the data properly retrieved. The retrieval is done by a class called "ProductData" As soon as it's ready, I want to navigate from the splash page to the main screen of the app.
Unfortunately, I can't find a good way to trigger a method that runs that kind of Navigation and listens to a Provider.
This is the code that I'm using to test this idea. Specifically, I want to run the command Navigator.pushNamed(context, 'home'); when the variable shouldProceed becomes true. Unfortunately, the code below gives me the error, "setState() or markNeedsBuild() called during build."
import 'package:catalogo/firebase/ProductData.dart';
import 'package:flutter/material.dart';=
import 'package:provider/provider.dart';
class RouteSplash extends StatefulWidget {
#override
_RouteSplashState createState() => _RouteSplashState();
}
class _RouteSplashState extends State<RouteSplash> {
bool shouldProceed = false;
#override
Widget build(BuildContext context) {
shouldProceed =
Provider.of<ProductData>(context, listen: true).shouldProceed;
if (shouldProceed) {
Navigator.pushNamed(context, 'home'); <-- The error occurs when this line is hit.
} else {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
}
}
Is there a better way to navigate to a page based on listening to the results of a provider?
Instead of trying to navigate to a new view what you should do is display the loading splash screen if you are still waiting for data and once that changes display your main home view, like this:
import 'package:catalogo/firebase/ProductData.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class Main extends StatefulWidget {
#override
_MainState createState() => _MainState();
}
class _MainState extends State<Main> {
bool shouldProceed = Provider.of<ProductData>(context, listen: true).shouldProceed;
#override
Widget build(BuildContext context) {
if(shouldProceed){
return Home();
}else{
return RouteSplash();
}
}
}
Use BlocListener as in this example:
BlocListener(
bloc: BlocProvider.of<DataBloc>(context),
listener: (BuildContext context, DataState state) {
if (state is Success) {
Navigator.of(context).pushNamed('/details');
}
},
child: BlocBuilder(
bloc: BlocProvider.of<DataBloc>(context),
builder: (BuildContext context, DataState state) {
if (state is Initial) {
return Text('Press the Button');
}
if (state is Loading) {
return CircularProgressIndicator();
}
if (state is Success) {
return Text('Success');
}
if (state is Failure) {
return Text('Failure');
}
},
}
)
Source: https://github.com/felangel/bloc/issues/201
I think I have a solution that does what the OP wants. If you make your splash screen to be Stateful, then you can add a PostFrameCallback. This avoids any problems with Navigator being called when build is running. Your callback can then call whatever routine Provider needs to read the data. This read data routine can be passed a further callback which contains the Navigator command.
In my solution I've added a further callback so that the splash screen is visible for at least one second (you can choose what duration you think reasonable here). Unfortunately, this creates a race condition, so I need to import the synchronized package to avoid problems.
import 'package:flutter/material.dart';
import 'package:reflect/utils/constants.dart';
import 'category_screen.dart';
import 'package:provider/provider.dart';
import 'package:reflect/data_models/app_prefs.dart';
import 'dart:async';
import 'dart:core';
import 'package:synchronized/synchronized.dart';
class LoadingScreen extends StatefulWidget {
static const id = 'LoadingScreen';
#override
_LoadingScreenState createState() => _LoadingScreenState();
}
class _LoadingScreenState extends State<LoadingScreen> {
bool readPrefsDone = false;
bool timeFinished = false;
Lock _lock = Lock();
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
Provider.of<AppPrefs>(context, listen: false).readPrefs(readDone);
Timer(Duration(seconds: 1), () {
timerDone();
});
});
}
void timerDone() async {
_lock.synchronized(() {
if (readPrefsDone) {
pushMainScreen();
}
timeFinished = true;
});
}
void readDone() {
_lock.synchronized(() {
if (timeFinished) {
pushMainScreen();
}
readPrefsDone = true;
});
}
void pushMainScreen() {
Navigator.pushReplacement(
context,
PageRouteBuilder(
pageBuilder: (context, animation, animation2) => CategoryScreen(),
transitionDuration: Duration(seconds: 1),
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.white,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Hero(
tag: kFishLogoTag,
child: Image(
image: AssetImage('assets/fish_logo.png'),
),
),
SizedBox(
height: 30,
),
Text(
'Reflect',
style: TextStyle(
fontSize: 30,
color: Color(0xFF0000cc),
fontWeight: FontWeight.bold,
),
),
],
),
),
));
}
}
Anyone else facing this issue can use this code
Future.delayed(Duration.zero, () => Navigate.toView(context));
This navigates to the other screen without build errors

How to use dart.core.sink in flutter

import 'package:flutter/material.dart';
import 'dart:async';
void main() => runApp(MyApp());
//Using Bloc
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: bloc.darkThemeEnabled,
initialData: false,
builder: (context, snapshot) => MaterialApp(
theme: snapshot.data ? ThemeData.dark() : ThemeData.light(),
home: HomePage(snapshot.data)),
);
}
}
class HomePage extends StatelessWidget {
final bool darkThemeEnabled;
HomePage(this.darkThemeEnabled);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Dynamic Theme"),
),
body: Center(
child: Text("Hello World"),
),
drawer: Drawer(
child: ListView(
children: <Widget>[
ListTile(
title: Text("Dark Theme"),
trailing: Switch(
value: darkThemeEnabled,
onChanged: bloc.changeTheme,
),
)
],
),
),
);
}
}
class Bloc {
final _themeController = StreamController<bool>();
get changeTheme => _themeController.sink.add;
get darkThemeEnabled => _themeController.stream;
}
final bloc = Bloc();
1.A warning says to Close instances of dart.core.sink
2.Why dart.core.sink is used in flutter?
3.How can I solve this error
4.Its error documentation redirects me to this website link
5.I don't know how to use these methods in flutter please guide me
dart.core.sink is an interface that is implemented by Stream.
The warning is showing, because the dart compiler wants you to .close() your instance of a Stream. In this case that is your final _themeController = StreamController<bool>().
If you want to fix the warning, add
void dispose() {
_themeController.close();
}
to your Bloc class.
Just adding the method is not doing much, since it's not called. So you should change your main() method to call bloc.dispose() after runApp(MyApp()).
That error occur when missing close StreamController.
Simple way to fix:
Create abstract class:
abstract class Bloc {
void dispose();
}
Your bloc class implements Bloc, now you can close StreamController in dispose:
class ColorBloc implements Bloc {
// streams of Color
StreamController streamListController = StreamController<Color>.broadcast();
// sink
Sink get colorSink => streamListController.sink;
// stream
Stream<Color> get colorStream => streamListController.stream;
// function to change the color
changeColor() {
colorSink.add(getRandomColor());
}
// Random Colour generator
Color getRandomColor() {
Random _random = Random();
return Color.fromARGB(
_random.nextInt(256),
_random.nextInt(256),
_random.nextInt(256),
_random.nextInt(256),
);
}
// close Stream
#override
void dispose() {
streamListController.close();
}
}