I've started using flutter_bloc package instead of redux to try it out, but I'm not entirely sure how I'm going to call flutter bloc events when receiving things from native (Android/iOS). It was easier with redux because in my parent MyApp widget of my main.dart file, I passed in the redux store to a custom class I created, and dispatched methods from the said class (called MethodChannelHandler).
main.dart:
void main() {
runApp(new MyApp());
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final Store<AppState> store = Store<AppState>(
// ... redux stuff ...
);
#override
void initState() {
// sauce
MethodChannelHandler(store);
super.initState();
}
}
methodChannelHandler.dart:
class MethodChannelHandler {
Store<AppState> store;
MethodChannelHandler(this.store) {
methodChannel.setMethodCallHandler(_handleMethod);
}
// Handle method calls from native
Future _handleMethod(MethodCall call) async {
if (call.method == A_METHOD) {
store.dispatch("something from native")
}
}
}
NOTE: I'm inept when it comes to programming vocabulary so please, if possible, please give me a small snippet of example code like I have or link me to some GitHub repo I can refer to instead of giving me a block of text I'm probably not going to understand.
In very simple way it's look like this:
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider<SomeBloc>(
create: (_) {
final bloc = SomeBloc(); //Create bloc
MethodChannelHandler(bloc); //Add method handler
return bloc;
},
lazy: false,
child: Text("Content"),
);
}
}
class SomeBloc extends Bloc {
SomeBloc() : super(SomeInitState());
#override
Stream mapEventToState(event) async* {
if (event is SomeEvent) {
//Handle SomeEvent
}
}
}
class MethodChannelHandler {
final SomeBloc someBloc;
MethodChannelHandler(this.someBloc) {
methodChannel.setMethodCallHandler(_handleMethod);
}
// Handle method calls from native
Future _handleMethod(MethodCall call) async {
if (call.method == A_METHOD) {
someBloc.add(SomeEvent("something from native"));
}
}
}
Related
I have a function called control in the StateFull Widget. I want to run this function with WorkManager every 15 minutes.
How can I call the control function from the callbackDispatcher function?
I added a Stream statically to the Statefull widget and then listened to it but it didn't work.
HomeScreen.dart file
import 'package:flutter/material.dart';
import 'package:workmanager/workmanager.dart';
const taskKontrol = "control";
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return Container();
}
#override
void initState() {
super.initState();
setupWorkManager();
}
void control() async
{
//... my code control is here
}
}
void setupWorkManager() async {
await Workmanager.initialize(callbackDispatcher, isInDebugMode: true);
Workmanager.registerPeriodicTask(taskKontrol, taskKontrol,
frequency: Duration(seconds: 10),
existingWorkPolicy: ExistingWorkPolicy.append
);
}
void callbackDispatcher() {
Workmanager.executeTask((taskName, inputData) async {
switch(taskName)
{
case taskKontrol:
// How can I call the control function from here?
print("control from workmanager");
break;
}
return Future.value(true);
});
}
For those who still looking for an answer:
From the official docs:
The callbackDispatcher needs to be either a static function or a top level function to be accessible as a Flutter entry point.
I had this same problem and I solved it by moving the function callbackDispatcher to the file: main.dart
Also, the code that initializes callbackDispatcher must be in main() before the App() widget loads.
To call your control code, create a class with static function control()
Note: You cannot call the widget's method from callbackDispatcher!
Reason: Widgets are UI bound. As long as the screen remains active, the widget that is visible remains active. Once you close the app or move on to next screen, the widgets' memory gets recycled. But this callbackDispatcher gets executed even when your app is closed. So, it has to be isolated from UI code.
Here's the code:
main.dart:
import 'package:flutter/material.dart';
import 'package:workmanager/workmanager.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Workmanager().initialize(callbackDispatcher, isInDebugMode: true);
runApp(App());
}
void callbackDispatcher() {
Workmanager.executeTask((taskName, inputData) async {
switch(taskName)
{
case ScheduledTask.taskName:
ScheduledTask.control(); // calls your control code
break;
}
return Future.value(true);
});
}
class ScheduledTask {
const static String taskName = "control";
static void control() {
// add your control here
}
}
All you can do from HomeScreen widget is to call setupWorkManager() that schedules the task
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return Container();
}
#override
void initState() {
super.initState();
setupWorkManager();
}
}
void setupWorkManager() async {
Workmanager.registerPeriodicTask(taskKontrol, taskKontrol,
frequency: Duration(minutes: 15),
existingWorkPolicy: ExistingWorkPolicy.append
);
}
Note: The minimum frequency for the recurring task is 15 minutes
I'm trying to figure out the BLoC library, but it gives me headaches.
I'm trying to fetch hotel names from an API. I have a model and a service responsible for contacting the API and fetching the data. However, I don't know how to connect it to the BLoC library.
Once my app starts, I want BLoC to fetch the data from the API and then show it in the app.
Here's my code:
hotel_model.dart
class Hotels {
final List<Hotel> hotels;
Hotels({this.hotels});
factory Hotels.fromJson(Map<String, dynamic> json) {
return Hotels(
hotels: List<Hotel>.from(
json['hotels'].map(
(x) => Hotel.fromJson(x),
),
),
);
}
}
class Hotel {
final String hotelName;
Hotel({this.hotelName});
factory Hotel.fromJson(Map<String, dynamic> json) {
return Hotel(
hotelName: json['name'],
);
}
}
hotel_service.dart
import 'package:http/http.dart' as http;
abstract class DownloadService {
Future<http.Response> fetchHotels();
}
class HotelService extends DownloadService {
#override
Future<http.Response> fetchHotels() {
final Uri uri = Uri.https('services.lastminute.com', 'mobile/stubs/hotels');
return http.get(uri);
}
}
And here's what I did wit the BLoC lib.
hotel_event.dart
part of 'hotel_bloc.dart';
#immutable
abstract class HotelEvent {}
class OnAppStartEvent extends HotelEvent {}
hotel_bloc.dart
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:hotels/models/hotel/hotel_model.dart';
import 'package:hotels/services/hotel/hotel_service.dart';
import 'package:meta/meta.dart';
part 'hotel_event.dart';
part 'hotel_state.dart';
class HotelBloc extends Bloc<HotelEvent, HotelState> {
HotelBloc() : super(HotelFinal());
final HotelService hotelService = HotelService();
#override
Stream<HotelState> mapEventToState(
HotelEvent event,
) async* {
if (event is FetchEvent) {
final response = hotelService.fetchHotels();
yield
}
}
}
hotel_state.dart
part of 'hotel_bloc.dart';
#immutable
abstract class HotelState {
HotelState();
}
class HotelFinal extends HotelState {
final Hotel hotel;
HotelFinal(this.hotel);
Hotel getHotel() {
return hotel;
}
}
First of all add await to this line in your bloc
final response = await hotelService.fetchHotels();
return List<Hotel> from your fetchHotels function
you must have stateful class for your screen and in the initState
you can create your bloc object and call .add method on it
in your build method wrap your widget with BlocBuilder and on builder callback check your bloc state, if the state is HotelFinal return your ui with list of hotels in your state object.
It'll be useful to add another state for your HotelState for when your bloc is fetching the data, and even for when there's an error. e.g;
part of 'hotel_bloc.dart';
#immutable
abstract class HotelState {
HotelState();
}
class HotelFinal extends HotelState {
final Hotel hotel;
HotelFinal(this.hotel);
Hotel getHotel() {
return hotel;
}
}
class HotelLoading extends HotelState {
HotelLoading();
}
class HotelError extends HotelState {
final String error;
HotelError(this.error);
}
You would want to change your mapEventToState to something like this:
#override
Stream<HotelState> mapEventToState(
HotelEvent event,
) async* {
if (event is FetchEvent) {
yield HotelLoading();
try {
final response = await hotelService.fetchHotels();
// It seems like your service doesn't return an hotel directly, so you'll have to deal with this as it is not part of the question.
final hotel = getYourHotelHereWithTheResponse;
yield HotelFinal(hotel);
} catch (e) {
yield HotelError('something went wrong getting the hotel info');
}
}
}
Lastly, add a widget to your widget tree that adds FetchEvent to your bloc and add a BlocBuilder to react to the change of states. Note that this is very flexible and can be done in many ways, but it is out of the scope of your very broad question, I'm just showing you how to use the library at a minimal:
class MyStatefulWidget extends StatefulWidget {
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
HotelBloc hotelBloc;
#override
void initState() {
hotelBloc = HotelBloc..add(FetchEvent());
super.initState();
}
#override
void dispose() {
hotelBloc.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return BlocBuilder(builder: (context, state) {
if(state is HotelLoading) {
// return a widget to deal with loading
}
if(state is HotelFinal) {
// return a widget to deal with success
}
if(state is HotelError) {
// return a widget to deal with error
}
});
}
}
i make an enum for a connectivity :
enum ConnectivityStatus{
Wifi,
Cellular,
Offline
}
then i create a service to check the Connectivity :
import 'dart:async';
import 'package:Zabatnee/activities_app/enum/connectivity_status.dart';
import 'package:connectivity/connectivity.dart';
class ConnectivityService{
StreamController<ConnectivityStatus> connectionStatusController = StreamController<ConnectivityStatus>();
ConnectivityService(){
Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
var connectionStatus = _getStatusFromResult(result);
connectionStatusController.add(connectionStatus);
});
}
ConnectivityStatus _getStatusFromResult(ConnectivityResult result) {
switch (result) {
case ConnectivityResult.mobile:
return ConnectivityStatus.Cellular;
case ConnectivityResult.wifi:
return ConnectivityStatus.Wifi;
case ConnectivityResult.none:
return ConnectivityStatus.Offline;
default:
return ConnectivityStatus.Offline;
}
}
}
and what i need is to check the connectivity status in all screen that i created. Can I do it once for all screens all, or i must check the connnectivity for each one individually.
You can check the connectivity status once & that will be implemented for all of your app.
You just have to create a wrapper class & subscribe to the Connectivity stream & apply the logic in this wrapper class.
Your whole widget will be wrapped by this widget.
MaterialApp(
..
home: ConnectivityWrapper(
childWidget: YourWidget(), // replace this with your own home widget
),
);
The wrapper widget will look something like this:
class ConnectivityWrapper extends StatefulWidget {
ConnectivityWrapper(this.childWidget);
final Widget childWidget;
#override
_ConnectivityWrapperState createState() => _ConnectivityWrapperState();
}
class _ConnectivityWrapperState extends State<ConnectivityWrapper> {
StreamSubscription<ConnectivityStatus> subscription;
#override
void initState() {
super.initState();
subscription = connectionStatusController.stream.listen((status)
{
if(status == ConnectivityStatus.Offline) {
// Your logic here (Toast message or something else)
}
},
onDone() {
// Your logic here
},
onError: () {
// Your logic here
});
}
#override
Widget build(BuildContext context) {
return widget.childWidget;
}
#override
void dispose() {
// unsubscribe to the stream
subscription.cancel();
super.dispose();
}
}
1.this is the main entry
void main() {
WidgetsFlutterBinding.ensureInitialized();
StorageUtil.getInstance();
runApp(MaterialApp(home: MyApp()));
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Wrapper(),
);
}
}
This is the Wrapper. The log-in form or the home page do not show unless I manually hot-reload the app.
I've tried everything but i am stuck. Please help.
class Wrapper extends StatefulWidget {
#override
_WrapperState createState() => _WrapperState();
}
class _WrapperState extends State<Wrapper> {
User _user = User();
#override
Widget build(BuildContext context) {
_user.uId = StorageUtil.getString('access_token');
if(_user.uId != null && _user.uId != ""){
print('filled ${_user.uId}');
return Home();
}else{
print('empty ${_user.uId}');
return Authenticate();
}
}
}
I think your StorageUtil is giving you promise for get data back to you but you are not waiting for it when app loads at first time.You can try await StorageUtil.getInstance(); in main block.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await StorageUtil.getInstance();
runApp(MaterialApp(home: MyApp()));
}
You need to watch the instance. Right now you are grabbing the instance to get the value but you are not subscribing to the value itself, which means that when the value changes nothing will happen until you refresh the page. I recommend subscribing to the value (access_token) that is determining the login screen vs the home screen.
Flutter has some built in features that makes this a bit easier such as streams and or quicker widgets like the ValueListenerBuilder. Let's see if we can do that with StorageUtil.
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await StorageUtil.getInstance();
runApp(MaterialApp(home: MyApp()));
}
class Wrapper extends StatefulWidget {
#override
_WrapperState createState() => _WrapperState();
}
class _WrapperState extends State<Wrapper> {
User _user = User();
#override
Widget build(BuildContext context) {
return ValueListenableBuilder(
valueListenable: StorageUtil.getString('access_token');,
builder: (BuildContext context, String value, _) {
if(value != null && value != ""){
print('filled ${_user.uId}');
return Home();
} else {
print('empty ${_user.uId}');
return Authenticate();
}
},
),
}
}
It is rough but it should get the job done! I recommend probably finding a more streamlined way to store your state than just the StorageUtil that'll better scale as your application grows.
I'm still a beginner with streams and bloc pattern.
I would like to do following:
Trigger an event.
Based on the event get back a state with an object
Store this object as JSON in a database.
All examples are showing, how an object can be displayed in a widget with BlocBuilder. But I don't need to display the value, only get it and store it. I can't figure out how to get the value into a variable.
How can I do that? In the View class I'm dispatching the event, but now I need to know how to get the object in the state back without using BlocBuilder.
Here are the details:
Bloc
class SchoolBloc extends Bloc<SchoolEvent, SchoolState> {
final SchoolRepository _schoolRepository;
StreamSubscription _schoolSubscription;
SchoolBloc({#required SchoolRepository schoolRepository})
: assert(schoolRepository != null),
_schoolRepository = schoolRepository;
#override
SchoolState get initialState => SchoolsLoading();
#override
Stream<SchoolState> mapEventToState(SchoolEvent event) async* {
if (event is LoadSchool) {
yield* _mapLoadSchoolToState();
Stream<SchoolState> _mapLoadSchoolToState(LoadSchool event) async* {
_schoolSubscription?.cancel();
_schoolSubscription = _schoolRepository.school(event.id).listen(
(school) {
SchoolLoaded(school);
}
);
}
Event
#immutable
abstract class SchoolEvent extends Equatable {
SchoolEvent([List props = const []]) : super(props);
}
class LoadSchool extends SchoolEvent {
final String id;
LoadSchool(this.id) : super([id]);
#override
String toString() => 'LoadSchool';
}
State
#immutable
abstract class SchoolState extends Equatable {
SchoolState([List props = const []]) : super(props);
}
class SchoolLoaded extends SchoolState {
final School school;
SchoolLoaded([this.school]) : super([school]);
#override
String toString() => 'SchoolLoaded { school: $school}';
}
View
class CourseView extends StatefulWidget {
#override
State<StatefulWidget> createState() => _CourseViewState();
}
class _CourseViewState extends State<CourseView> {
#override
initState() {
super.initState();
print("this is my init text");
final _schoolBloc = BlocProvider.of<SchoolBloc>(context);
_schoolBloc.dispatch(LoadSchool("3kRHuyk20UggHwm4wrUI"));
// Here I want to get back the school object and save it to a db
}
Test that fails
For testing purposes I have done following:
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:teach_mob/core/blocs/school/school.dart';
class CourseView extends StatefulWidget {
#override
State<StatefulWidget> createState() => _CourseViewState();
}
class _CourseViewState extends State<CourseView> {
#override
void initState() {
super.initState();
BlocProvider.of<SchoolBloc>(context)
.dispatch(LoadSchool("3kRHuyk20UggHwm4wrUI"));
}
#override
Widget build(BuildContext context) {
return BlocListener<SchoolBloc, SchoolState>(
listener: (context, state) {
print("BlocListener is triggered");
},
child: Text("This is a test")
);
}
}
The LoadSchool event is triggered. The text in the child attribute of BlocListener is displayed, but the listener function that should print "BlocListener is triggered" is not executed.
Use BlocListener. It is meant to be used for those cases you mention.