Thank you for checking out my question :). I appreciate it!
What I am trying to do
I am trying to check if the user has an internet connection or not. For this, I use the bloc pattern. I am just starting out and I don't know what to do with these errors.
What the error messages are
The relevant error-causing widget was:
TestScreen
Testscreen:file//filepath (etc,etc)
Another exception was thrown: Each child must be laid out exactly once.
Another exception was thrown: Updated layout information required for RenderErrorBox NEEDS-LAYOUT NEEDS-PAINT to calculate semantics.
Another exception was thrown: Bad state: Future already completed.
I am new and these errors are overwhelming. These errors tell me nothing. I hope you can help me out!
Code
Homescreen
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
static String routeName = '/home';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Homepage'),
),
body: BlocProvider(
create: (context) => NetworkBloc()..add(ListenConnection()),
child: TestScreen(),
));
}
}
class TestScreen extends StatelessWidget {
const TestScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: BlocBuilder<NetworkBloc, NetworkState>(
builder: (context, state) {
if (state is ConnectionFailure) return Text("No Internet Connection");
if (state is ConnectionSuccess)
return Text("You're Connected to Internet");
else
return Text("");
},
),
);
}
}
Network Block
class NetworkBloc extends Bloc<NetworkEvent, NetworkState> {
NetworkBloc() : super(ConnectionInitial());
late StreamSubscription _subscription;
#override
Stream<NetworkState> mapEventToState(NetworkEvent event) async* {
if (event is ListenConnection) {
_subscription = DataConnectionChecker().onStatusChange.listen((status) {
add(ConnectionChanged(status == DataConnectionStatus.disconnected
? ConnectionFailure()
: ConnectionSuccess()));
});
}
if (event is ConnectionChanged) yield event.connection;
}
#override
Future<void> close() {
_subscription.cancel();
return super.close();
}
}
Network Event
abstract class NetworkEvent {}
class ListenConnection extends NetworkEvent {}
class ConnectionChanged extends NetworkEvent {
NetworkState connection;
ConnectionChanged(this.connection);
}
Network State
abstract class NetworkState {}
class ConnectionInitial extends NetworkState {}
class ConnectionSuccess extends NetworkState {}
class ConnectionFailure extends NetworkState {}
I hope you can help me out. Thank you very much!
There are a lot of issues with your code so I'm not sure if I got all of them.
You should not call .add() if you don't have registered event handler
//This is how you should register an event handler
NetworkBloc() : super(ConnectionInitial()) {
on<ListenConnection>((event, emit) {
// TODO: implement event handler
});
}
You are overriding a function mapEventToState(), when it doesn't override any of inherited methods
//#override NO NEED OF OVERRIDE HERE
Stream<NetworkState> mapEventToState(NetworkEvent event) async* {
if (event is ListenConnection) {
_subscription = DataConnectionChecker().onStatusChange.listen((status) {
add(ConnectionChanged(status == DataConnectionStatus.disconnected
? ConnectionFailure()
: ConnectionSuccess()));
});
}
if (event is ConnectionChanged) yield event.connection;
}
And I'm not sure if this is intended, but your mapEventToState() also never gets called and without any emit() methods your UI will never rebuild.
Related
I define a model reactively with GetX and send this model reactively to the view with the help of StateMixin. But this variable I sent changes the main variable as well. How exactly does this happen and how can I fix it? In the example I gave below, when I change the id value, the id automatically changes in the rawMyModel model. But I don't want it to change.
detail_controller.dart
class DetailController extends GetxController with StateMixin<Rx<MyModel>> {
late final MyModel rawMyModel;
#override
void onInit() async {
super.onInit();
rawMyModel = (Get.arguments as MyModel);
change(Rx(rawMyModel), status: RxStatus.success());
}
void reset() {
change(Rx(rawMyModel), status: RxStatus.success());
}
}
detail_page.dart
class DetailPage extends GetView<DetailController> {
const DetailPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: controller.obx((Rx<MyModel>? myModel) => _buildBody(myModel: myModel!)),
);
}
Widget _buildBody({required Rx<MyModel> myModel}) {
print(myModel.value.toString());
myModel.update((val) => val.id = 5); // change
}
}
I am trying to fetch data from API as soon as the flutter app loads but I am unable to achieve so
class MarketBloc extends Bloc<MarketListEvent, MarketListState> {
MarketBloc() : super(MarketLoading()) {
on<MarketSelectEvent>((event, emit) async {
emit(MarketLoading());
final data = await ApiCall().getData(event.value!);
globalData = data;
emit(MarketDataFetched(marDat: globalData.data, dealType: event.value));
});
}
}
I have called MarketLoading state as the initial state and I want to call MarketSelectEvent just after that but in the current code, action is required to do so and i want to achieve it without any action.
You have 2 options:
add an event from the UI as soon you instantiate the MarketBloc
MarketBloc()..add(MarketSelectEvent())
add an event in the initialization code
MarketBloc() : super(MarketLoading()) {
add(MarketSelectEvent());
}
You could do this with in the initState of whatever the first page is that your app loads.
class TestPage extends StatefulWidget {
#override
State<TestPage> createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
late MarketBloc marketBloc;
#override
void initState() {
super.initState();
marketBloc = BlocProvider.of<MarketBloc>(context);
marketBloc.add(MarketSelectEvent());
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: BlocBuilder<MarketBloc, MarketListState>(
builder: (context, state) {
if (state is MarketLoading) {
return Text('loading...');
}
if (state is MarketDataFetched) {
return ...your UI that contains data from API call
}
},
),
),
);
}
}
The following code was working before null safety with flutter_bloc 4.0.1 but after null safety migration the state is not updating / emitting / broadcasting as expected with flutter_bloc 7.3.3.
The below _reactToState and mapEventToState methods are never called. How can I fix it?
Splash Screen
class SplashScreen extends StatefulWidget {
final Strapper strapper;
final Service? service;
SplashScreen(this.strapper, this.service);
#override
State<StatefulWidget> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
SplashBloc? _splashBloc;
#override
void didChangeDependencies() {
super.didChangeDependencies();
if (_splashBloc == null) {
_splashBloc = SplashBloc(widget.strapper, widget.service);
_splashBloc!.stream.listen(_reactToState);
}
}
#override
dispose() {
_splashBloc?.close();
_splashBloc = null;
super.dispose();
}
#override
Widget build(BuildContext context) {
return BlocProvider<SplashBloc>(
create: (context) => _splashBloc!,
child: BlocBuilder<SplashBloc, SplashBlocState>(
builder: (context, state) => Container(
child: Stack(
children: <Widget>[
LogoPanel(
_showWidgetForState(state),
),
],
),
),
),
);
}
void _reactToState(SplashBlocState state) {
if (state is InitializingSplashBlocState) {
if (widget.logOut) {
_splashBloc!.add(LogoutSplashBlocEvent());
} else {
_splashBloc!.add(CInitializationSplashBlocEvent());
}
} else if (state is AuthSuccessSplashBlocState) {
App.navigateToSomewhere(context, state.isNewUser);
}
}
Widget _showWidgetForState(SplashBlocState state) {
if (state is InitializingSplashBlocState) {
return _getProgressIndicator();
} else if (state is ChooseSomethingSplashBlockState ) {
return _showSignInWidget();
}
}
}
Splash Bloc
class SplashBloc extends Bloc<SplashBlocEvent, SplashBlocState> {
final Strapper? strapper;
final Service? service;
SplashBloc(this.strapper, this.service) : super(InitializingSplashBlocState());
#override
Stream<SplashBlocState> mapEventToState(event) async* {
if (event is CInitializationSplashBlocEvent) {
await strapper!.run();
}
bool chooseSomething = !service!.hasSomeSelection;
if (chooseSomething) {
yield ChooseSomethingSplashBlockState();
} else if (event is RAuthSplashBlocEvent) {
yield AuthSplashBlocState();
var authState = await _run();
yield authState;
}
}
Future<SplashBlocState> _run() async {
// Do something
}
}
Splash Bloc Event
abstract class SplashBlocEvent extends Equatable {
const SplashBlocEvent();
#override
List<Object> get props => [];
}
class CInitializationSplashBlocEvent extends SplashBlocEvent {}
class RAuthSplashBlocEvent extends SplashBlocEvent {}
Splash Bloc State
abstract class SplashBlocState extends Equatable {
const SplashBlocState();
#override
List<Object> get props => [];
}
class InitializingSplashBlocState extends SplashBlocState {}
class AuthSplashBlocState extends SplashBlocState {}
class ChooseSomethingSplashBlockState extends SplashBlocState {}
class AuthSuccessSplashBlocState extends SplashBlocState {
final CurrentUser? user;
final bool isNewUser;
AuthSuccessSplashBlocState(this.user, this.isNewUser);
}
As per the documentation:
In v6.0.0, the above snippet does not output the initial state and only outputs subsequent state changes. The previous behavior can be achieved with the following:
final bloc = MyBloc();
print(bloc.state);
bloc.listen(print);
So I changed my code in the Splash screen as following:
#override
void didChangeDependencies() {
super.didChangeDependencies();
if (_splashBloc == null) {
_splashBloc = SplashBloc(widget.strapper, widget.service);
_reactToState(_splashBloc!.state); // Added this line
_splashBloc!.stream.listen(_reactToState);
}
}
And that's it. It worked!
_reactToState and mapEventToState are definitely being called.
when you use Streamcontrollers it greatly simplifies state. I build a bloc code to manage state. The materialapp child is the splashWidget whose job is to render the hour, minute, second from bloc code emitting Time state. If the user clicks the splash screen or 5 seconds elapses the splash screen will be replaced with the HomePageWidget. bloc code controls the starting and stopping of the timer using an timerState event.
'package:flutter/material.dart';
import 'bloc_splash.dart';
import 'main.dart';
class SplashWidget extends StatelessWidget {
const SplashWidget({Key? key}) : super(key: key);
_redirectToHome(BuildContext context)
{
Navigator.pushReplacement(context,MaterialPageRoute(builder:(_)=>MyHomePage(title:"helloWorld")));
}
String _displayClock(Time ? data)
{
String retVal="";
if (data!=null)
{
retVal="Time: ${data.hour} : ${data.minute} : ${data.second}";
}
return retVal;
}
#override
Widget build(BuildContext context) {
SplashBloc _bloc=SplashBloc();
_bloc.timerOnChange(StartTimer());
return Scaffold(
body:InkWell(
onTap: (){_bloc.timerOnChange(StopTimer());
_redirectToHome(context);
},
child:Container(
child:
StreamBuilder<TimeState>(
stream:_bloc.timeStream,
builder:(context,snapshot)
{
if(snapshot.hasData && (snapshot.data is RedirectState))
{
return MyHomePage(title:"helloWorld");
}
return Center(child:Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("Splash Screen", style:TextStyle(fontSize: 24,fontWeight: FontWeight.bold)),
Text(_displayClock(snapshot.data?.time)),
]));
}
)
))
);
}
}
bloc code
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:rxdart/rxdart.dart';
import 'dart:ui';
import 'dart:async';
abstract class TimerEvent extends Equatable{
const TimerEvent();
#override
List<Object>get props=>[];
}
class StartTimer extends TimerEvent{
const StartTimer();
}
class StopTimer extends TimerEvent{
const StopTimer();
}
class Time{
final int hour;
final int minute;
final int second;
Time(this.hour,this.minute,this.second);
}
class TimeState extends Equatable{
final Time time;
TimeState(this.time);
#override
List<Object> get props=>[time];
}
class RedirectState implements TimeState{
final Time time;
RedirectState(this.time);
#override
List<Object> get props=>[time];
#override
// TODO: implement stringify
bool? get stringify => throw UnimplementedError();
}
class TimerState extends Equatable{
final bool started;
const TimerState(this.started);
#override
List<Object> get props => [started];
}
class SplashBloc
{
SplashBloc();
Timer ?_timer;
var countDown=5;
Stream<TimeState> get timeStream=> _timeController.stream;
final _timeController =BehaviorSubject<TimeState>();
void dispose()
{
_timeController.close();
}
void _pushTimeOnTheStream(Timer timer)
{
DateTime now=DateTime.now();
_timeController.sink.add(TimeState(Time(now.hour,now.minute,now.second)));
this.countDown-=1;
if (this.countDown==0)
{
timerOnChange(StopTimer());
_timeController.sink.add(RedirectState(Time(0,0,0)));
}
}
void timerOnChange(TimerEvent event) {
if (event is StartTimer)
{
_timer=Timer.periodic(Duration(seconds: 1),_pushTimeOnTheStream);
}
else if(event is StopTimer){
//_timerController.sink.add(TimerState(false));
_timer?.cancel();
}
}
}
app
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const SplashWidget(),
);
}
}
example_states:
abstract class ExampleState extends Equatable {
const ExampleState();
}
class LoadingState extends ExampleState {
//
}
class LoadedState extends ExampleState {
//
}
class FailedState extends ExampleState {
//
}
example_events:
abstract class ExampleEvent extends Equatable {
//
}
class SubscribeEvent extends ExampleEvent {
//
}
class UnsubscribeEvent extends ExampleEvent {
//
}
class FetchEvent extends ExampleEvent {
//
}
example_bloc:
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
#override
ExampleState get initialState => LoadingState();
#override
Stream<ExampleState> mapEventToState(
ExampleEvent event,
) async* {
if (event is SubscribeEvent) {
//
} else if (event is UnsubscribeEvent) {
//
} else if (event is FetchEvent) {
yield LoadingState();
try {
// network calls
yield LoadedState();
} catch (_) {
yield FailedState();
}
}
}
}
example_screen:
class ExampleScreenState extends StatelessWidget {
// ignore: close_sinks
final blocA = ExampleBloc();
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocBuilder<ExampleBloc, ExampleState>(
bloc: blocA,
// ignore: missing_return
builder: (BuildContext context, state) {
if (state is LoadingState) {
blocA.add(Fetch());
return CircularProgressBar();
}
if (state is LoadedState) {
//...
}
if (state is FailedState) {
//...
}
},
),
);
}
}
As you can see in example_bloc, initial state is LoadingState() and in build it shows circular progress bar. I use Fetch() event to trigger next states. But I don't feel comfortable using it there. What I want to do is:
When app starts, it should show LoadingState and start networking calls, then when it's all completed, it should show LoadedState with networking call results and FailedState if something goes wrong. I want to achieve these without doing
if (state is LoadingState) {
blocA.add(Fetch());
return CircularProgressBar();
}
Your discomfort really has reason - no event should be fired from build() method (build() could be fired as many times as Flutter framework needs)
Our case is to fire initial event on Bloc creation
Possibilities overview
case with inserting Bloc with BlocProvider - this is preferred way
create: callback is fired only once when BlocProvider is mounted & BlocProvider would close() bloc when BlocProvider is unmounted
class ExampleScreenState extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocProvider(
create: (context) => ExampleBloc()..add(Fetch()), // <-- first event,
child: BlocBuilder<ExampleBloc, ExampleState>(
builder: (BuildContext context, state) {
...
},
),
),
);
}
}
case when you create Bloc in State of Statefull widget
class _ExampleScreenStateState extends State<ExampleScreenState> {
ExampleBloc _exampleBloc;
#override
void initState() {
super.initState();
_exampleBloc = ExampleBloc();
_exampleBloc.add(Fetch());
// or use cascade notation
// _exampleBloc = ExampleBloc()..add(Fetch());
}
#override
void dispose() {
super.dispose();
_exampleBloc.close(); // do not forget to close, prefer use BlocProvider - it would handle it for you
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocBuilder<ExampleBloc, ExampleState>(
bloc: _exampleBloc,
builder: (BuildContext context, state) {
...
},
),
);
}
}
add first event on Bloc instance creation - this way has drawbacks when testing because first event is implicit
class ExampleBloc extends Bloc<ExampleEvent, ExampleState> {
...
ExampleBloc() {
add(Fetch());
}
}
// insert it to widget tree with BlocProvider or create in State
BlocProvider( create: (_) => ExampleBloc(), ...
// or in State
class _ExampleScreenStateState extends State<ExampleScreenState> {
final _exampleBloc = ExampleBloc();
...
PS feel free to reach me in comments
Sergey Salnikov has a great answer. I think I can add another suggestion however.
In my main.dart file I am using a MultiBlocProvider to create all my blocs for use further down the tree. Like so
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: <BlocProvider<dynamic>>[
BlocProvider<OneBloc>(create: (_) => OneBloc()),
BlocProvider<TwoBloc>(create: (_) => TwoBloc()),
],
child: MaterialApp( // Rest of your app )
Then when I need to call an event when I load a page, in this case I wanted to fetch some data depending on a list tile selected, and I needed more options than FutureBuilder can provide me, I simple used initState(); and called the bloc provider and added an event.
class _ExampleScreenState extends State<ExampleScreen> {
#override
void initState() {
super.initState();
BlocProvider.of<OneBloc>(context)
.add(FetchData);
}
It works because the bloc has already been provided from the root widget.
In simple terms:
Using BlocProvider, call it during creation.
BlocProvider(create: (context) => ExampleBloc()..add(Fetch()))
Using BlocState, use it as
class _ExampleScreenStateState extends State<ExampleScreenState> {
ExampleBloc _exampleBloc;
#override
void initState() {
super.initState();
_exampleBloc = ExampleBloc()..add(Fetch());
}
#override
void dispose() {
super.dispose();
_exampleBloc.close();
}
I have 4 classes SignUp , Auth, PageOne and InWidget (inherited widget). In the classe signUpState i have a swiper that i can control using a controller.
SignUp
class SignUp extends StatefulWidget {
static const String id = 'history_page';
#override
SignUpState createState() => SignUpState();
goto(bool x) => createState().goto(x);
}
SignUpState
class SignUpState extends State<SignUp> {
SwiperController _swOneCtrl;
#override
void initState() {
_swOneCtrl = new SwiperController();
super.initState();
}
goto(bool anim){
_swOneCtrl.next(animation: anim);
print("goto fired");
}
}
Auth
class Auth extends StatelessWidget {
SignUp s = SignUp();
verifyPhoneNumber() {
s.goto(true);
}
}
PageOne
class PageOneState extends State<PageOne> {
#override
Widget build(BuildContext context) {
final MyInheritedWidgetState state = MyInheritedWidget.of(context);
return RaisedButton(
color: Colors.blueGrey,
disabledColor: Colors.grey[100],
textColor: Colors.white,
elevation: 0,
onPressed: !phonebtn
? null
: () {
final MyInheritedWidgetState state =
MyInheritedWidget.of(context);
state.verifyPhoneNumber();
},
child: Text("CONTINUER"),
),
);
}
}
The thing is i want to call verifyPhoneNumber() from auth that will call the goto() method from pageone using inwidget as intermediary but i'm getting this error :
Unhandled Exception: NoSuchMethodError: The method 'next' was called on null.
do you know why ?
Try to initialize at the time of declaration.
class SignUpState extends State<SignUp> {
SwiperController _swOneCtrl = new SwiperController();
#override
void initState() {
super.initState();
}
goto(bool anim){
_swOneCtrl.next(animation: anim);
print("goto fired");
}
}
Respond me if it works.
initState() is a method that is called once when the stateful widget is inserted in the widget tree.
We generally override this method if we need to do some sort of initialization work like registering a listener because unlike build() this method is called once.
As I think you are declaring Swipe controller in your SignUPState class.
class SignUpState extends State<SignUp> {
SwiperController _swOneCtrl;
#override
void initState() {
_swOneCtrl = new SwiperController();
super.initState();
}
goto(bool anim){
_swOneCtrl.next(animation: anim);
print("goto fired");
}
}
But you have initialized it in initState(). the problem is because you are not inserting your SignUp widget in widget tree so your swipe controller is not initializing and become null. So when you are calling the next method to null it is showing error.
As Solution first insert your Sign up widget in your Widget tree.
if my solution helped you. Please rate me.