I'm building an app which fetches hotel names from an API. I'm using the BLoC library. I managed to create whole service which downloads the data, but the result doesn't show in my terminal.
My BLoC works, it downloads the data. I saw it in Dart DevTools, but the state doesn't change and it does not show up.
Here's my code:
hotel_bloc.dart
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:hotels/controllers/hotel/hotel_controller.dart';
import 'package:hotels/models/hotel/hotel_model.dart';
import 'package:meta/meta.dart';
part 'hotel_event.dart';
part 'hotel_state.dart';
class HotelBloc extends Bloc<HotelEvent, HotelState> {
HotelBloc() : super(HotelLoading());
final HotelController hotelController = HotelController();
#override
Stream<HotelState> mapEventToState(
HotelEvent event,
) async* {
if (event is FetchEvent) {
yield HotelLoading();
try {
final Hotels hotels = await hotelController.parseHotels();
yield HotelFinal(hotels);
} catch (error) {
HotelError(error);
}
}
}
}
hotel_state.dart
part of 'hotel_bloc.dart';
#immutable
abstract class HotelState {
HotelState();
}
class HotelFinal extends HotelState {
final Hotels hotels;
HotelFinal(this.hotels);
Hotels getHotel() {
return hotels;
}
}
class HotelLoading extends HotelState {
HotelLoading();
}
class HotelError extends HotelState {
final String error;
HotelError(this.error);
}
hotel_event.dart
part of 'hotel_bloc.dart';
#immutable
abstract class HotelEvent {
HotelEvent();
}
class FetchEvent extends HotelEvent {
FetchEvent();
}
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);
}
}
hotel_controller.dart
import 'package:hotels/models/hotel/hotel_model.dart';
import 'package:hotels/services/hotel/hotel_service.dart';
class HotelController {
final HotelService hotelService = HotelService();
Future<Hotels> parseHotels() async {
final response = await hotelService.fetchHotels();
final hotels = hotelsFromJson(response.body);
return hotels;
}
}
And finally the HomeScreen
home_screen.dart
import 'package:flutter/material.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hotels/blocs/hotel/hotel_bloc.dart';
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
HotelBloc hotelBloc;
#override
void initState() {
hotelBloc = HotelBloc()..add(FetchEvent());
super.initState();
}
#override
void dispose() {
hotelBloc.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('title').tr(),
),
body: BlocConsumer<HotelBloc, HotelState>(
listener: (context, state) {
if (state is HotelError) {
print(state.error);
}
},
builder: (context, state) {
if (state is HotelLoading) {
print('It\'s loading!');
}
if (state is HotelFinal) {
print(state.hotels.toString());
}
return Text('Default text');
},
),
);
}
}
The result is this:
The problem is the you haven't provided the BlocConsumer with your hotelBloc. You want to either have BlocProvider as a parent or use the cubit parameter on BlocConsumer.
BlocConsumer<HotelBloc, HotelState>(
cubit: hotelBloc,
listener:...
builder:...
)
Related
I have a bloc listening to chats. Through the debugger, I can see that the bloc is catching live updates. However, the ui is not rebuilding when these changes occur. To see a change, I have to leave and reload the screen.
My chat state:
part of 'chat_bloc.dart';
abstract class ChatState extends Equatable {
const ChatState();
#override
List<Object?> get props => [];
}
class ChatLoading extends ChatState {}
class ChatLoaded extends ChatState {
final List<Chat?>? compiledChats;
const ChatLoaded({required this.compiledChats});
#override
List<Object?> get props => [compiledChats];
}
My chat events:
part of 'chat_bloc.dart';
abstract class ChatEvent extends Equatable {
const ChatEvent();
#override
List<Object?> get props => [];
}
class LoadChat extends ChatEvent {
const LoadChat();
#override
List<Object> get props => [];
}
class CloseChat extends ChatEvent {
const CloseChat();
#override
List<Object?> get props => [];
}
class UpdateChat extends ChatEvent {
final List<List<Chat?>> chats;
const UpdateChat({required this.chats});
#override
List<Object> get props => [chats];
}
My actual bloc:
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:hero/blocs/auth/auth_bloc.dart';
import 'package:hero/models/chat_model.dart';
import 'package:hero/models/user_model.dart';
import 'package:hero/repository/firestore_repository.dart';
part 'chat_event.dart';
part 'chat_state.dart';
class ChatBloc extends Bloc<ChatEvent, ChatState> {
final FirestoreRepository _firestoreRepository;
late StreamSubscription _chatListener;
ChatBloc({
required FirestoreRepository firestoreRepository,
}) : _firestoreRepository = firestoreRepository,
super(ChatLoading()) {
on<LoadChat>(_onLoadChat);
on<UpdateChat>(_onUpdateChat);
on<CloseChat>(_onCloseChat);
}
void _onLoadChat(
LoadChat event,
Emitter<ChatState> emit,
) {
_chatListener = _firestoreRepository.chats.listen((chats) {
add(
UpdateChat(
chats: chats,
),
);
});
}
void _onUpdateChat(
UpdateChat event,
Emitter<ChatState> emit,
) {
//generate compiledChats from event.chats
List<Chat?>? compiledChats = [];
for (List<Chat?> chatList in event.chats) {
for (Chat? chat in chatList) {
if (chat != null) {
compiledChats.add(chat);
}
}
}
emit(ChatLoaded(compiledChats: compiledChats));
}
void _onCloseChat(
CloseChat event,
Emitter<ChatState> emit,
) {
_chatListener.cancel();
print('ChatBloc disposed');
emit(ChatLoading());
}
#override
Future<void> close() async {
super.close();
}
}
For the scope of this problem, all that really matters is just looking at the updateChat and LoadChat methods. Any ideas? Thanks!
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(),
);
}
}
I'm create a project on Flutter. And I'm using a provider to change screens in my app.
Here is my main.dart file:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:school_app/services/auth_service.dart';
import 'package:school_app/wrapper.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => AuthService().auth,
child: MaterialApp(
home: Wrapper(),
),
);
}
}
Also this is my wrapper.dart file where the screens choose:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:school_app/screens/authenticate/auth.dart';
import 'package:school_app/models/user.dart';
import 'package:school_app/screens/school/home.dart';
import 'package:school_app/services/auth_service.dart';
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<AuthProvider>(context);
print(user.auth);
if(!user.auth) return Auth();
return Home();
}
}
And it is my AuthProvider class:
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AuthService {
/* AuthUser _user(User user) {
return user != null ? AuthUser(uid: user.uid) : null;
}*/
AuthProvider auth = new AuthProvider();
//sign in
Future signIn(String username, String password) async {
try {
SharedPreferences prefs = await SharedPreferences.getInstance();
var dio = Dio();
Response user = await dio.post('url', data: {
'username': username,
'password': password
});
if(user.data['success'] == false) return user.data['msg'];
await prefs.setString('token', user.data['token']);
auth.setAuth(true);
print("SUCCESS");
} catch(e) {
print('Error ' + e.toString());
}
}
}
class AuthProvider with ChangeNotifier {
bool _auth;
AuthProvider() {
_auth = false;
}
bool get auth => _auth;
void setAuth(bool auth) {
_auth = auth;
notifyListeners();
}
}
And when I call a function in AuthProvider class setAuth, nothing changed. Can you help me and find my mistake?
EDIT
I'm making all changes that you writes but it is not working. Here is my main.dart:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:school_app/services/auth_service.dart';
import 'package:school_app/wrapper.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => AuthProvider(),
child: MaterialApp(
home: Wrapper(),
),
);
}
}
Also wrapper.dart:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:school_app/screens/authenticate/auth.dart';
import 'package:school_app/screens/school/home.dart';
import 'package:school_app/services/auth_service.dart';
class Wrapper extends StatefulWidget {
#override
_WrapperState createState() => _WrapperState();
}
class _WrapperState extends State<Wrapper> {
#override
void initState() {
// TODO: implement initState
super.initState();
AuthService().auth;
}
#override
Widget build(BuildContext context) {
return Consumer<AuthProvider>(builder: (context, authProvider, child) {
print(authProvider.auth);
if (!authProvider.auth) {
return Auth();
} else {
return Home();
}
});
}
}
And AuthService and AuthProvider classes:
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
class AuthService {
/* AuthUser _user(User user) {
return user != null ? AuthUser(uid: user.uid) : null;
}*/
AuthProvider auth = new AuthProvider();
//sign in
Future signIn(String username, String password) async {
try {
SharedPreferences prefs = await SharedPreferences.getInstance();
var dio = Dio();
Response user = await dio.post('url', data: {
'username': username,
'password': password
});
if(user.data['success'] == false) return user.data['msg'];
await prefs.setString('token', user.data['token']);
auth.setAuth(true);
print("SUCCESS");
} catch(e) {
print('Error ' + e.toString());
}
}
}
class AuthProvider with ChangeNotifier {
bool _auth;
AuthProvider() {
_auth = false;
}
bool get auth => _auth;
void setAuth(bool auth) {
_auth = auth;
notifyListeners();
}
}
Notice, that here two classes and in AuthService I'm calling function .setAuth(true).
In your current implementation of Wrapper, you are rendering the widget once and not listening to whether the values changed. You could use Consumer as suggested above. You could also choose to watch the value for changes - like this:
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = context.watch<AuthProvider>();
print(user.auth);
if(!user.auth) return Auth();
return Home();
}
}
When you use a watch or Consumer pattern, the widget will be rendered when the values of the underlying store (which is AuthProvider here) gets changed.
The only missing part here is that you never Consume the AuthProvider to listen to the notifyListeners() trigger.
The correct implementation looks like the following (I didn't try it, you may have to correct some typo errors, but you'll get the idea !)
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<AuthProvider>(
builder: (context, authProvider, child) {
if (!authProvider.auth) {
return Auth();
} else {
return Home();
}
}
);
}
}
EDIT
I didn't notice you weren't injecting the right Class in your ChangeNotifierProvider. You'll also have to update your widget MyApp
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => AuthProvider(),
child: MaterialApp(
home: Wrapper(),
),
);
}
And in this case you probably should transform your Wrapper widget to a Stateful widget, and in the initState method you should call AuthService().auth.
I strongly recommend you to read the official documentation of Provider, looks like things aren't crystal clear yet in your mind
EDIT 2
You're still missing the point of the Provider library.
The goal of this lib is to provide an instance of a class to your widget tree so you don't have to re-create an instance in each widget.
Here, in AuthService class you're re-creating a AuthProvider with AuthProvider auth = new AuthProvider(); instead of referring to the existing instance created in the parent Widget.
To refer to a previously created instance, you should use Provider.of<AuthProvider>(context); in the AuthService class, or, even better, pass the instance of AuthProvider as a parameter in the signIn method.
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
}
});
}
}
Im extending the native implementation of FirebaseMessagingService to wait for push notifications in native android.
I need to start my flutter app when the user clicks the push notification, so.. How can I send data to my flutter app?
In Flutter
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget{
#override
Widget build(BuildContext context) {
return MaterialApp(
home: ScreenPage(),
);
}
}
class ScreenPage extends StatefulWidget {
#override
_ScreenPageState createState() => _ScreenPageState();
}
class _ScreenPageState extends State<ScreenPage> {
static const platform = const MethodChannel("myChannel");
#override
void initState() {
platform.setMethodCallHandler(nativeMethodCallHandler);
super.initState();
}
Future<dynamic> nativeMethodCallHandler(MethodCall methodCall) async {
print('Native call!');
switch (methodCall.method) {
case "methodNameItz" :
return "This data from flutter.....";
break;
default:
return "Nothing";
break;
}
}
#override
Widget build(BuildContext context) {
//return ();
}
}
In Java
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import io.flutter.app.FlutterActivity;
import io.flutter.plugin.common.MethodChannel;
//import io.flutter.view.FlutterNativeView;
public class MyJavaFile extends FlutterActivity {
Button clickMeButton;
MethodChannel channel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
channel = new MethodChannel(getFlutterView(), "myChannel");
setContentView(R.layout.home_activity);
clickMeButton = findViewById(R.id.clickMeButton);
clickMeButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
channel.invokeMethod("methodNameItz", null, new MethodChannel.Result() {
#Override
public void success(Object o) {
Log.d("Results", o.toString());
}
#Override
public void error(String s, String s1, Object o) {
}
#Override
public void notImplemented() {
}
});
}
});
}
}
You can still basically look at the firebase messaging example (or the same for android)
Create a Platform Channel in your AppDelegate (or MainActivity on android) and the register for the same channel on the dart/flutter side (maybe main.dart and register a method like onPushClicked)
Listen for your parse messages in your native code
Send them to your dart code using the platform channel (as seen in the above linked example) channel.invokeMethod('onPushClicked', myMessageArguments)
flutter
Map<String,dynamic> data = <String,dynamic>{};
final res = await methodChannel.invokeMethod('getDistance');
data = Map<String,dynamic>.from(res);
final dis = data['distance'] as String;
final tim = data['time'] as String;
ios native
switch call.method {
case "getDistance":
let data =
["distance":routeFormattedDistance,"time":routeFormattedTravelTime]
result(data)
default :
result("")
}