Emit was called after an event handler completed normally [Flutter] - flutter

Exception is thrown on second call 'emit' inside 'firstEvent' case. I know why, but I dont know how to make it work. Problem exists when I use event.map.
_AssertionError
emit was called after an event handler completed normally.
This is usually due to an unawaited future in an event handler.
Please make sure to await all asynchronous operations with event handlers
and use emit.isDone after asynchronous operations before calling emit() to
ensure the event handler has not completed.
void main() {
runApp(const MyApp());
}
#freezed
class SampleEvent with _$SampleEvent {
const factory SampleEvent.firstEvent() = FirstEvent;
const factory SampleEvent.secondEvent() = SecondEvent;
}
#freezed
class SampleState with _$SampleState {
const factory SampleState.initialState() = InitialState;
const factory SampleState.firstState() = OneFirstState;
const factory SampleState.secondState() = SecondState;
}
class SampleBloc extends Bloc<SampleEvent, SampleState> {
SampleBloc() : super(const SampleState.initialState()) {
on<SampleEvent>(eventHandler);
}
FutureOr<void> eventHandler(
SampleEvent event,
Emitter<SampleState> emit,
) async {
event.map(
firstEvent: (value) async {
emit(const SampleState.firstState());
await Future.delayed(const Duration(seconds: 2));
// EXCEPTION!!! HERE:
emit(const SampleState.secondState());
},
secondEvent: (value) => null,
);
}
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: BlocProvider(create: (_) => SampleBloc(), child: const Home()),
);
}
}
class Home extends StatelessWidget {
const Home({super.key});
#override
Widget build(BuildContext context) {
return BlocConsumer<SampleBloc, SampleState>(
builder: (context1, state) {
return TextButton(
onPressed: () => context1.read<SampleBloc>().add(
const SampleEvent.firstEvent(),
),
child: const Text('Launch First Event'),
);
},
listener: (context, state) {},
);
}
}

Related

Provider: How can I `notifyListener()` within a `StreamBuilder()`? It causes the error `setState() or markNeedsBuild() called during build`

I have a Provider model such as
provider_model.dart:
import 'package:flutter/material.dart';
class ProviderModel extends ChangeNotifier {
final List<String> _myList = [];
List<String> get myList => [..._myList];
void addItem(String item) {
_myList.add(item);
notifyListeners();
}
}
Now, Flutter documentation shows us how to listen to websockets. Here I am using their example together with my ProviderModel():
main.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import './provider_model.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: ChangeNotifierProvider(
create: (BuildContext context) => ProviderModel(),
child: Scaffold(
body: MyHomePage(),
)),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _controller = TextEditingController();
final _channel = WebSocketChannel.connect(
Uri.parse('wss://echo.websocket.events'),
);
#override
Widget build(BuildContext context) {
return Consumer<ProviderModel>(
builder: (context, provider, _) {
return Column(children: [
TextField(
controller: _controller,
decoration: InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter a search term',
suffixIcon: IconButton(
icon: Icon(
Icons.send,
),
onPressed: _sendMessage)),
),
...provider.myList.map((e) => Text(e)).toList(),
StreamBuilder(
stream: _channel.stream,
builder: (context, snapshot) {
if (snapshot.hasData) {
// ERROR HERE!
provider.addItem('I was added');
return Text("Item added");
} else if (snapshot.hasError) {
return Text(snapshot.error as String);
} else {
return CircularProgressIndicator();
}
},
)
]);
},
);
}
void _sendMessage() {
if (_controller.text.isNotEmpty) {
_channel.sink.add(_controller.text);
print('done');
}
}
}
Which the following output (Chrome):
Now, when I click on the send button, it calls _sendMessage() (code above). And then since the StreamBuilder() hasData it runs this line (code above):
provider.addItem('I was added');
However, this is where my error appears, I am getting the following error:
The following assertion was thrown while dispatching notifications for ProviderModel:
setState() or markNeedsBuild() called during build.
Why am I getting this error? Where is the widget rebuilding? See this answer to Flutter Provider setState() or markNeedsBuild() called during build
You can take help from addPostFrameCallback, but the cost is it will keep rebuilding on every frame, to control this behavior you can use a bool on state class.
bool isDone = false;
void addItemH() {
if (!isDone) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
Provider.of<ProviderModel>(context, listen: false)
.addItem('I was added');
isDone = true;
});
}
}
And builder
builder: (context, snapshot) {
if (snapshot.hasData) {
addItemH();
return Text("Item added");
And to control the next insertaion.
void _sendMessage() {
if (_controller.text.isNotEmpty) {
isDone = false;
_channel.sink.add(_controller.text);
print('done');
}
}
}
If you just want to add data only single time, it is better to do inside inside initState.

How to call a method from Bloc on button click?

I am using Bloc to check internet connection. If there is no connection, I show the SnackBar. But I also need to be able to reuse the connection method to re-check the connection by clicking on the button, but I don’t understand how to call this method. Tell me how to call the connection method when the button is clicked?
bloc
class ConnectedBloc extends Bloc<ConnectedEvent, ConnectedState> {
StreamSubscription? subscription;
ConnectedBloc() : super(ConnectedInitial()) {
on<OnConnectedEvent>((event, emit) => emit(ConnectedSucess()));
on<OnNotConnectedEvent>((event, emit) => emit(ConnectedFailure()));
void connection() => Connectivity()
.onConnectivityChanged
.listen((ConnectivityResult result) {
if (result == ConnectivityResult.wifi ||
result == ConnectivityResult.mobile) {
add(OnConnectedEvent());
} else {
add(OnNotConnectedEvent());
}
});
home
home: BlocConsumer<ConnectedBloc, ConnectedState>(
listener: ((context, state) {
if (state is ConnectedSucess) {
const SizedBox();
} else if (state is ConnectedFailure) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
duration: const Duration(seconds: 3),
backgroundColor: Colors.transparent,
elevation: 0,
content: SystemMessagesSnackBar(
message: 'No internet access. Check your connection',
textButton: 'Refresh',
onPressed: () =>
ScaffoldMessenger.of(context).hideCurrentSnackBar(),
icon: SvgPicture.asset(constants.Assets.no_connection),
),
),
);
}
}),
To call a method inside your bloc, you need to get a reference to your bloc first by using context.read<T>(). In your case:
context.read<ConnectedBloc>()
You can then call the method as follows:
onPressed:(){
ScaffoldMessenger.of(context).hideCurrentSnackBar();
context.read<ConnectedBloc>().connection();
}
But this will create an additional stream. Maybe you should use checkConnectivity if you click on the button.
wrap the code with a streambuilder and then associate the bloc streams with the streambuilder
in your button click event interact with the bloc class
check the streambuilder state and process the returning data.
yaml
dependencies:
flutter:
sdk: flutter
equatable: ^2.0.3
rxdart: ^0.27.4
import 'package:flutter/material.dart';
import 'package:equatable/equatable.dart';
import 'package:rxdart/rxdart.dart';
void main() {
runApp(const MyApp());
}
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: 'Button Stream',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Test_StreambuilderButton(),
);
}
}
class Test_StreambuilderButton extends StatefulWidget {
Test_StreambuilderButton({Key? key}) : super(key: key);
BlocCode bloc= new BlocCode();
#override
State<Test_StreambuilderButton> createState() => _Test_StreambuilderButtonState();
}
class _Test_StreambuilderButtonState extends State<Test_StreambuilderButton> {
#override
Widget build(BuildContext context) {
return
Scaffold(appBar: AppBar(title:Text("Button Stream Event")),
body:
Column(children: [
StreamBuilder<BlocState>(
stream: widget.bloc.blocStream,
builder:(context,snapshot)
{
if (snapshot.hasData)
{
String data=snapshot.data!._message;
if (data == null)
{
return (Container(child:Text("No data")));
}
return (Container(child:Text(data)));
}
else
{
return (Container(child:Text("No activity")));
}
}
),
ElevatedButton(onPressed:(){
widget.bloc.setMessage(BlocState("Hello World"));
}, child: Text("Press Me"))
]));
}
}
class BlocState extends Equatable
{
BlocState(this._message);
final String _message;
#override
List<Object> get props=>[_message];
String get getMessage {return _message;}
}
class BlocCode
{
BlocCode();
Stream<BlocState> get blocStream => _loadController.stream;
final _loadController=BehaviorSubject<BlocState>();
void dispose()
{
_loadController.close();
}
setMessage(BlocState state)
{
_loadController.sink.add(state);
}
}

Firebase Firestore query with GetX and Firebase Auth

I have a small flutter application that uses Firebase Auth to login and then uses bindStream to query a list of documents from Firestore. It works from a fresh start/hot restart, but as soon as I logout I get a firebase/firestore permission error and subsequent login's don't refresh the stream. I thought that a GetxController disposes streams created via bindStream when the view that uses the controller is disposed. In this case, when I logout I pop off all routes via Get.offAll, but it appears the stream is still active and that's when the permissions error happens. But I'm not actually sure what is happening.
main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
Get.put(LoginController());
Get.put(AuthController(), permanent: true);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return GetMaterialApp(
debugShowCheckedModeBanner: false,
title: 'GetX Firebase Firestore',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SplashScreen(),
);
}
}
auth_controller.dart
class AuthController extends GetxController {
final AuthService _authService = AuthService();
AuthService get authService => _authService;
final LoginController _loginController = Get.find<LoginController>();
LoginController get loginController => _loginController;
Rxn<User> _user = Rxn<User>();
User? get user => _user.value;
#override
void onReady() async {
// bind auth state to _firebaesUser, but also give an initial value
_user = Rxn<User>(_authService.currentUser);
_user.bindStream(_authService.authState);
//run every time auth state changes
ever<User?>(_user, handleAuthChanged);
super.onReady();
}
handleAuthChanged(User? user) {
print("handleAuthChanged - ${user?.uid}");
if (user == null) {
Get.offAll(() => LoginScreen());
} else {
Get.offAll(() => HomeScreen(), binding: HomeBinding());
}
}
}
user_controller.dart
class UserController extends GetxController {
final UserRepository _userRepository = UserRepository();
final repository = UserRepository();
final users = Rx<List<FirestoreUser>>([]);
late Rx<FirestoreUser> _firestoreUser;
FirestoreUser get firestoreUser => _firestoreUser.value;
#override
void onInit() {
super.onInit();
final user = FirebaseAuth.instance.currentUser;
if (user == null) return;
_firestoreUser = Rx<FirestoreUser>(FirestoreUser.fromAuth(user));
// get user data from firestore
_firestoreUser.bindStream(_userRepository.getUserById(user.uid));
// query user collection
getAllUsers();
}
void getAllUsers() {
users.bindStream(repository.getAllUsers());
}
}
home_screen.dart
class HomeScreen extends GetView<UserController> {
const HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("All Sample Users"),
actions: [
IconButton(
onPressed: () => Get.to(ProfileScreen()),
icon: Icon(Icons.person),
),
],
),
body: Obx(
() => ListView.builder(
itemCount: controller.users.value.length,
itemBuilder: (context, index) {
final user = controller.users.value[index];
return ListTile(
leading: CircleAvatar(
backgroundImage: NetworkImage(user.photoURL),
),
title: Text(user.displayName),
);
},
),
),
);
}
}
home_binding.dart
class HomeBinding extends Bindings {
#override
void dependencies() {
Get.lazyPut<UserController>(() => UserController(), fenix: true);
}
}

How can I initialize my app with provider?

I choose to use provider as my state management so I saw I have to use Multi provider.
My struggle is how to architect my code that I can initialize all the data I need when my app first run and give the providers to the multi provider.
Provider example
import 'package:cron/cron.dart';
import 'package:flutter/material.dart';
import 'package:web_app/models/fixture.dart';
import 'package:web_app/services/fixture_service.dart';
class HighlightsProvider extends ChangeNotifier {
final List<Fixture> _highlights = [];
List<Fixture> get() => _highlights;
Future<void> fetchHighlights() async {
try {
List<Fixture> highlightFixtures = [];
final response = await FixtureService().getAppHighlightFixtures();
[...response].asMap().forEach((index, element) {
highlightFixtures.add(new Fixture.fromJson(element));
});
_highlights.clear();
_highlights.addAll(highlightFixtures);
notifyListeners();
} catch (e) {
print('error');
print(e);
}
}
runJob(cron) {
cron.schedule(Schedule.parse('* * * * *'), () async {
fetchHighlights();
print('fetch highlights every one minute');
});
}
}
Let's say this class will get all my providers and initialize theme:
class InitializeApp {
final cron = Cron();
Future run(HighlightsProvider highlightsProvider) async {
return Future.wait([
initiakizeHighlights(highlightsProvider),
]);
}
Future initiakizeHighlights(HighlightsProvider highlightsProvider) async {
highlightsProvider.runJob(cron);
await highlightsProvider.fetchHighlights();
}
}
Then I have to deliver those provider to the multi provider:
void main() async {
final highlightsProvider = HighlightsProvider();
await InitializeApp().run(highlightsProvider);
print('ready');
runApp(MyApp(highlightsProvider: highlightsProvider));
}
class MyApp extends StatelessWidget {
final highlightsProvider;
const MyApp({Key key, this.highlightsProvider}) : super(key: key);
#override
Widget build(BuildContext context) {
print('build');
return MultiProvider(
providers: [
ChangeNotifierProvider<HighlightsProvider>.value(
value: highlightsProvider,
)
],
child: MaterialApp(
title: 'tech',
theme: ThemeData(
primarySwatch: Colors.amber,
brightness: Brightness.light,
),
routes: <String, WidgetBuilder>{
'/': (BuildContext context) {
return MyHomePage(title: 'Flutter Demo Home Page');
}
}),
);
}
}
Normally you just wrap your MaterialApp with the MultiProvider, then you already have access to all Providers you will define.
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<RecipeStreamService>.value(value: RecipeStreamService().controllerOut)
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Home Food',
routes: {
'/register': (BuildContext context) => RegisterPage(),
'/login': (BuildContext context) => LoginPage()
},
),
home: HomePage(title: 'Home'),
),
);
}

unable to send messages From SendPort to ReceivePort in flutter

I'm trying to send a message via the IsolateNameServer in flutter.
However, I am unable to receive messages. I've tried checking if the receiver port is listening by calling port.listen() twice, and it says that it is already listening.
Where am I going wrong? [ I am closely following this documentation ]
Here is my main.dart, based on the above doc:
import 'dart:isolate';
import 'dart:math';
import 'dart:ui';
import 'package:android_alarm_manager/android_alarm_manager.dart';
import 'package:flutter/material.dart';
final ReceivePort port = ReceivePort();
const String isolateName = 'isolate';
main() async {
WidgetsFlutterBinding.ensureInitialized();
IsolateNameServer.registerPortWithName(
port.sendPort,
isolateName,
);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
accentColor: Colors.pink,
floatingActionButtonTheme:
FloatingActionButtonThemeData(backgroundColor: Colors.pink)),
home: AlarmManagerExample());
}
}
class AlarmManagerExample extends StatefulWidget {
#override
_AlarmManagerExampleState createState() => _AlarmManagerExampleState();
}
class _AlarmManagerExampleState extends State<AlarmManagerExample> {
#override
void initState() {
super.initState();
AndroidAlarmManager.initialize();
port.listen((_) async => await workForMe());
}
workForMe() async {
print("Secondary Function Triggered!");
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
child: RaisedButton(
onPressed: () async {
await AndroidAlarmManager.oneShot(const Duration(seconds: 5),
Random().nextInt(pow(2, 21)), callback);
},
child: Text(
"Alarm Manager",
),
),
),
);
}
static SendPort uiSendPort;
static callback() {
print("Callbacks Triggered!");
// This will be null if we're running in the background.
uiSendPort ??= IsolateNameServer.lookupPortByName(isolateName);
uiSendPort?.send(null);
}
}
The output I am getting is :
Callbacks Triggered!
However, the expected output is:
Callbacks Triggered!
Secondary Function Triggered!
I do believe that error is caused because the workForMe() function isn't static. Moreover, I don't know if callBacks support Future or async.
With that said, try this, as there is no need to make it async.
static void workForMe() {
print("Secondary Function Triggered!");
}