the method "sendOtp" isn't defined for class 'LoginPage' - flutter

i am calling this method(sendOtp) from another class but getting error: the method "sendOtp" isn't defined for class 'LoginPage'
it is a phone authentication loginpage method to sendOtp
class LoginPage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return LoginPageState();
}
}
class LoginPageState extends State<LoginPage> {
Future<void> sendOtp() async {
final PhoneVerificationCompleted phoneVerificationCompleted =
(AuthCredential credential) {};
final PhoneVerificationFailed phoneVerificationFailed =
(AuthException exception) {
print("Login Faild due to $exception");
};
final PhoneCodeSent phoneCodeSent =
(String verificationId, [int forceResendingToken]) {
this.verificationId = verificationId;
};
final PhoneCodeAutoRetrievalTimeout phoneCodeAutoRetrievalTimeout =
(String verificationId) {
this.verificationId = verificationId;
print("time out");
};
await _auth.verifyPhoneNumber(
phoneNumber: _numberController.text,
timeout: Duration(seconds: 120),
verificationCompleted: phoneVerificationCompleted,
verificationFailed: phoneVerificationFailed,
codeSent: phoneCodeSent,
codeAutoRetrievalTimeout: phoneCodeAutoRetrievalTimeout);
}
----------------ANOTHER CLASS-------------------
class OtpScreen extends StatefulWidget {
final String _name,_number, _id;
OtpScreen(this._name, this._number, this._id);
#override
OtpScreenState createState() {
return OtpScreenState();
}
}
class OtpScreenState extends State<OtpScreen> {
final TextEditingController _otpController = TextEditingController();
var _auth = FirebaseAuth.instance;
LoginPage loginPage = LoginPage();
Widget resend() {
return FlatButton(
child: Text("Send Otp again"),
onPressed: () => loginPage.sendOtp(), //ERROR IS HERE: sendOtp isn't defined for class LoginPage
);
}
}

You can't just call a method of another class without any reference. If the method is in the parent widget, you can pass it down when calling the child widget. If the method is in the child, you can pass a GlobalKey from the parent to the child, add that key to the child, and then through the key call the methods of the child.
If the two widgets have no relation what-so-ever, then you would benefit from using an InheritedWidget or a State Management solution that allows you to pass methods around in your app.

Related

Flutter bloc 8.x authentication not updating

Im experimenting and learning on a small project and im trying to implement an authentication workflow. Ive implemented everything and by printing my steps everything should just work fine. Im wondering why my BlocBuilder isn't going to update. The project is really small so I can provide you everything easy. Since im learning bloc, i appreciate every hint, approach and I want to thank you in advance.
terminal output when the app is starting:
flutter: building main.dart
flutter: AppLoaded()
flutter: user is NOT signed in
flutter: false
flutter: Transition { currentState: AuthInitial(), event: AppLoaded(), nextState: UnauthenticatedState() }
flutter: block says user is NOT authenticated
This is completely fine since im checking at the beginning if there is any user data valid. Now when I press on the Login Textbutton in my home.dart my Blocbuilder should show that im logged in, but it doesnt. This is the terminal output:
flutter: AppLoaded()
flutter: signed id with credentials: User{id: 1, socketId: 123, userName: Logged in User}
flutter: user is signed in
flutter: true
flutter: currentuser is not empty: User{id: 1, socketId: 123, userName: Logged in User}
flutter: Transition { currentState: AuthInitial(), event: AppLoaded(), nextState: AuthenticatedState() }
flutter: block says user is authenticated
main.dart
import 'package:fl_auth/bloc/auth/auth_bloc.dart';
import 'package:fl_auth/repositories/user_repository.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'bloc/bloc_observer.dart';
import 'home.dart';
import 'models/auth.dart';
void main() {
BlocOverrides.runZoned(
() {
runApp(const MyApp());
},
blocObserver: SimpleBlocObserver(),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
Auth _auth = Auth.instance;
UserRepository _userRepository = UserRepository(auth: _auth);
print('building main.dart');
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: BlocProvider(
create: (context) =>
AuthBloc(userRepository: _userRepository)..add(AppLoaded()),
child: Home(),
),
);
}
}
home.dart
import 'package:flutter/material.dart';
import 'package:flutter/src/foundation/key.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'bloc/auth/auth_bloc.dart';
import 'models/auth.dart';
import 'repositories/user_repository.dart';
class Home extends StatelessWidget {
const Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
Auth _auth = Auth.instance;
UserRepository _userRepository = UserRepository(auth: _auth);
AuthBloc authBloc = AuthBloc(userRepository: _userRepository);
return Scaffold(
body: SizedBox(
height: 500,
child: Column(
children: [
Container(
height: 200,
child: BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
if (state is UnauthenticatedState) {
return Center(child: Text('User is unauthenticated'));
} else if (state is AuthenticatedState) {
return Center(child: Text('YEAH logged in!'));
} else {
return Center(child: Text('something went wrong'));
}
}),
),
TextButton(
onPressed: () => {
authBloc.userRepository.signIn(),
authBloc.add(AppLoaded())
},
child: Text('Login')),
],
)));
}
}
auth_event.dart
part of 'auth_bloc.dart';
abstract class AuthEvent extends Equatable {
const AuthEvent();
#override
List<Object> get props => [];
}
class AppLoaded extends AuthEvent {}
auth_state.dart
// ignore_for_file: public_member_api_docs, sort_constructors_first
part of 'auth_bloc.dart';
abstract class AuthState extends Equatable {
const AuthState();
#override
List<Object> get props => [];
}
class AuthInitial extends AuthState {}
class AuthenticatedState extends AuthState {
User user;
AuthenticatedState({
required this.user,
});
}
class UnauthenticatedState extends AuthState {}
auth.bloc
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:fl_auth/repositories/user_repository.dart';
import '../../models/user.dart';
part 'auth_event.dart';
part 'auth_state.dart';
class AuthBloc extends Bloc<AuthEvent, AuthState> {
UserRepository userRepository;
AuthBloc({required this.userRepository}) : super(AuthInitial()) {
on<AppLoaded>((event, emit) async {
try {
var isSignedIn = await userRepository.isSignedIn();
print(isSignedIn);
if (isSignedIn) {
var user = await userRepository.getCurrentUser();
emit(AuthenticatedState(user: user));
print('block says user is authenticated');
} else {
emit(UnauthenticatedState());
print('block says user is NOT authenticated');
}
} catch (e) {
emit(UnauthenticatedState());
}
});
}
}
user_repository.dart
import 'package:fl_auth/models/user.dart';
import '../models/auth.dart';
class UserRepository {
Auth auth = Auth.instance;
UserRepository({required this.auth});
// sign in with username
//TODO: change hardcoded username to email and passwort later on
Future<User> signIn() async {
try{
await Future.delayed(Duration(seconds: 1));
var credentials = User(id: 1, socketId: '123', userName: 'Logged in User');
print('signed id with credentials: ${auth.currentUser}');
auth.currentUser = credentials;
return auth.currentUser;
}catch(e){
print(e.toString());
throw e;
}
}
// check signed in status
Future<bool> isSignedIn() async {
try{
await Future.delayed(const Duration(seconds: 1));
var currentUser = auth.currentUser;
if(currentUser.isNotEmpty){
print('user is signed in');
return true;
} else {
print('user is NOT signed in');
return false;
}
}catch(e){
print(e.toString());
throw e;
}
}
// get user
Future<User> getCurrentUser() async {
try{
await Future.delayed(const Duration(seconds: 1));
var currentUser = auth.currentUser;
if(currentUser.isNotEmpty){
print('currentuser is not empty: $currentUser');
return currentUser;
} else {
var message = 'User is empty';
print('currentuser IS empty: $currentUser');
throw message;
}
}catch(e){
print(e.toString());
throw e;
}
}
}
auth.dart
import 'user.dart';
class Auth {
/// private constructor
Auth._();
/// the one and only instance of this singleton
static final instance = Auth._();
//ChatBloc chatBloc = ChatBloc(DatabaseApi.db);
// Create a User instance. Actually it would be better if this is empty so I can notice if a user is valid or not and can react by checking if the user has values and
// if not log the user out later on
User currentUser = User.empty;
}
user.dart
import 'package:equatable/equatable.dart';
/// {#template user}
/// User model
///
/// [User.empty] represents an unauthenticated user.
/// {#endtemplate}
class User extends Equatable {
/// {#macro user}
const User({
required this.id,
this.socketId,
this.userName,
});
/// The current user's scoket id.
final String? socketId;
/// The current user's id.
final int id;
/// The current user's name (display name).
final String? userName;
/// Empty user which represents an unauthenticated user.
static const empty = User(id: 0);
/// Convenience getter to determine whether the current user is empty.
bool get isEmpty => this == User.empty;
/// Convenience getter to determine whether the current user is not empty.
bool get isNotEmpty => this != User.empty;
#override
List<Object?> get props => [id, socketId, userName];
// Convert a user into a Map. The keys must correspond to the names of the
// columns in the database.
Map<String, dynamic> toMap() {
return {
'id': id,
'socketId': socketId,
'userName': userName,
};
}
factory User.fromMap(Map<String, dynamic> map) {
return User(
id: map['id'] as int,
socketId: map['socketId'] as String,
userName: map['userName'] as String,
);
}
// Implement toString to make it easier to see information about
// each user when using the print statement.
#override
String toString() {
return 'User{id: $id, socketId: $socketId, userName: $userName}';
}
}
The issue is In your OnTap Function you can call Event like this. I have checked and It's working as expected.
TextButton(
onPressed: () => {
authBloc.userRepository.signIn(),
context.read<AuthBloc>().add(AppLoaded())
},
child: const Text('Login')),

How to implement BlocTest function?

I'm trying to implement blocTesting for my flutter app starting with authentication feature. Below are the Authentication and login related files required for this. I'd really appreciate if someone could show me on how I can implement blocTesting based on my code because I've been facing problems in doing so. Below are the bloc, state and event files for the auth bloc.
Authbloc.dart
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
part 'authentication_event.dart';
part 'authentication_state.dart';
class AuthenticationBloc extends Bloc<AuthenticationEvent, AuthenticationState> {
final AuthenticationRepository authenticationRepository = AuthenticationRepository();
final SettingsRepository _settingsRepository = SettingsRepository();
AuthenticationBloc() : super(AuthenticationInitial()) {
// Register events here
on<AuthenticationStarted>(_onAuthenticationStarted);
on<AuthenticationLoggedIn>(_onAuthenticationLoggedIn);
on<AuthenticationLoggedOut>(_onAuthenticationLoggedOut);
}
Future<void> _onAuthenticationStarted(AuthenticationStarted event, Emitter<AuthenticationState> emit) async {
try {
final bool hasToken = await authenticationRepository.hasToken();
if (hasToken) {
final Settings _settings = await _settingsRepository.getSettings();
final SysConfig _sysConfig = await _settingsRepository.getSysconfig();
final CountriesModelList _countries = await _settingsRepository.getCountries();
final ReasonsModelList _reasons = await _settingsRepository.getReasons();
final NotificationOptionsList _notificationOptions = await _settingsRepository.getNotificationOptions();
emit(
AuthenticationLoadSuccess(
settings: _settings,
sysConfig: _sysConfig,
countries: _countries,
reasons: _reasons,
notificationOptions: _notificationOptions,
),
);
} else {
emit(AuthenticationUnauthenticated());
}
} catch (e) {
final MYException _exception = e as MYException;
emit(AuthenticationLoadFailure(exception: _exception));
}
}
Future<void> _onAuthenticationLoggedIn(AuthenticationLoggedIn event, Emitter<AuthenticationState> emit) async {
emit(AuthenticationLoadInProgress());
await authenticationRepository.persistToken(event.token);
final Settings _settings = await _settingsRepository.getSettings();
final SysConfig _sysConfig = await _settingsRepository.getSysconfig();
final CountriesModelList _countries = await _settingsRepository.getCountries();
final ReasonsModelList _reasons = await _settingsRepository.getReasons();
final NotificationOptionsList _notificationOptions = await _settingsRepository.getNotificationOptions();
emit(
AuthenticationLoadSuccess(
settings: _settings,
sysConfig: _sysConfig,
countries: _countries,
reasons: _reasons,
notificationOptions: _notificationOptions,
),
);
}
Future<void> _onAuthenticationLoggedOut(AuthenticationLoggedOut event, Emitter<AuthenticationState> emit) async {
await authenticationRepository.deleteToken();
await Future<dynamic>.delayed(const Duration(seconds: 2));
emit(AuthenticationUnauthenticated());
add(AuthenticationStarted());
}
}
Authstate.dart
part of 'authentication_bloc.dart';
abstract class AuthenticationEvent extends Equatable {
const AuthenticationEvent();
#override
List<Object> get props => <Object>[];
}
class AuthenticationStarted extends AuthenticationEvent {}
class AuthenticationLoggedIn extends AuthenticationEvent {
final String token;
const AuthenticationLoggedIn({required this.token});
#override
List<Object> get props => <Object>[token];
}
class AuthenticationLoggedOut extends AuthenticationEvent {}
AuthEvent.dart
part of 'authentication_bloc.dart';
abstract class AuthenticationState extends Equatable {
const AuthenticationState();
#override
List<Object> get props => <Object>[];
}
class AuthenticationInitial extends AuthenticationState {}
class AuthenticationUnauthenticated extends AuthenticationState {}
class AuthenticationLoadSuccess extends AuthenticationState {
final SysConfig sysConfig;
final Settings settings;
final CountriesModelList countries;
final ReasonsModelList reasons;
final NotificationOptionsList notificationOptions;
const AuthenticationLoadSuccess({required this.sysConfig, required this.settings, required this.countries, required this.reasons, required this.notificationOptions});
#override
List<Object> get props => <Object>[sysConfig, settings, countries, reasons, notificationOptions];
}
class AuthenticationLoadInProgress extends AuthenticationState {}
class AuthenticationLoadFailure extends AuthenticationState {
final MYException exception;
const AuthenticationLoadFailure({required this.exception});
#override
List<Object> get props => <Object>[exception];
}
you have to change a lot of thinks.
First of all you need to add the repository/ies to your bloc constructor to inject the mocks.
class AuthenticationBloc extends Bloc<AuthenticationEvent, AuthenticationState> {
late final AuthenticationRepository authenticationRepository;
final SettingsRepository _settingsRepository = SettingsRepository();
AuthenticationBloc({required this.authenticationRepository}) : super(AuthenticationInitial()) {
// Register events here
on<AuthenticationStarted>(_onAuthenticationStarted);
on<AuthenticationLoggedIn>(_onAuthenticationLoggedIn);
on<AuthenticationLoggedOut>(_onAuthenticationLoggedOut);
}
Then you can use the mock when creating the bloc in the setup method
setUp(() {
authenticationRepositoryMock = MockWeatherRepository();
authenticationBloc = AuthenticationBloc(authenticationRepository: authenticationRepositoryMock );
});
Then you have to return that bloc in the build function of your blocTest and also you have to setup the mock behavior there
build: () {
when(() => authenticationRepositoryMock .hasToken()).thenAnswer((_) async => true);
return bloc;
},
Then add an event to your bloc in the act function
act: (dynamic b) => b.add(AuthenticationStarted()),
And then you can check the result in the expect function. (i think the initial state will not be emitted here)
expect: () => [
AuthenticationLoadSuccess(...),
It also a good idea to mock the SettingsRepository.

is this the right way to implement a custom architecture in flutter app using riverpod?

I am creating an App using riverpod, hooks and freezed. My aim is to remove the entire logic out of the widget tree. This article by #ttlg inspired me. In my app, I am trying to implement the following pattern. I am pretty new to riverpod so am i doing something wrong? You have my thanks.
what I am trying to achieve - image
my PasswordInputWidget
Widget build(BuildContext context, WidgetRef ref) {
final _authState = ref.watch(loginScreenStateProvider);
final _authController = ref.watch(authControllerProvider);
final _textController = useTextEditingController();
bool _isPasswordObscured = _authState.isPasswordObscured;
return TextField(
controller: _textController,
obscureText: _isPasswordObscured,
decoration: InputDecoration(
label: Text(AppLocalizations.of(context)!.password),
border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
suffixIcon: IconButton(
onPressed: () => _authController.toggleObscurePassword(),
icon: _isPasswordObscured
? const Icon(Icons.visibility_off_rounded)
: const Icon(Icons.visibility_rounded))),
onChanged: (value) =>
_authController.inputPassword(textController: _textController));
}
the logic is kept inside a file named auth_controller.dart as shown below
the controller updates the auth_state using user inputs.
The controller also handles the data coming from the repository which in turn updates the state.
abstract class AuthController {
void inputUserName({required TextEditingController textController});
void inputPassword({required TextEditingController textController});
void toggleObscurePassword();
bool checkUserNameisValid(String userName);
void login();
}
class AuthControllerImpl implements AuthController {
final Reader _read;
AuthControllerImpl(this._read);
#override
void inputUserName({required TextEditingController textController}) {
final state = _read(loginScreenStateProvider);
bool isValidUserName = checkUserNameisValid(textController.text);
_read(loginScreenStateProvider.notifier).setLoginScreenState(state.copyWith(
userName: textController.text, isValidUserName: isValidUserName));
}
#override
void toggleObscurePassword() {
final state = _read(loginScreenStateProvider);
_read(loginScreenStateProvider.notifier).setLoginScreenState(
state.copyWith(isPasswordObscured: !state.isPasswordObscured));
}
#override
void inputPassword({required TextEditingController textController}) {
final state = _read(loginScreenStateProvider);
_read(loginScreenStateProvider.notifier)
.setLoginScreenState(state.copyWith(password: textController.text));
}
#override
bool checkUserNameisValid(String userName) {
return TextUtils.isValidInput(
text: userName, exp: AppConstants.EMAIL_VALIDATOR);
}
#override
void login() {
final state = _read(loginScreenStateProvider);
final repository = _read(authRepositoryProvider);
if (state.userName.isNotEmpty &&
state.isValidUserName &&
state.password.isNotEmpty) {
repository.login(userName: state.userName, password: state.password);
}
}
}
final authControllerProvider = StateProvider.autoDispose<AuthControllerImpl>(
(ref) => AuthControllerImpl(ref.read));
The state is kept in a separate file auth_state.dart and is exposed using a getter and setter.
class LoginScreenState extends StateNotifier<LoginScreenModel> {
LoginScreenState() : super(const LoginScreenModel());
LoginScreenModel getLoginScreenState() {
return state;
}
void setLoginScreenState(LoginScreenModel newloginScreenState) {
state = newloginScreenState;
}
}
final loginScreenStateProvider =
StateNotifierProvider.autoDispose<LoginScreenState, LoginScreenModel>(
(ref) => LoginScreenState());
The model of the state is created using freezed as shown below
import 'package:freezed_annotation/freezed_annotation.dart';
part 'auth_model.freezed.dart';
#freezed
class LoginScreenModel with _$LoginScreenModel {
const factory LoginScreenModel(
{#Default('') String userName,
#Default('') String password,
#Default(true) bool isValidUserName,
#Default(true) bool isPasswordObscured,
#Default(false) bool showInvalidUserNameError}) = _LoginScreenModel;
}
API calls are handled in the repository as shown below.
//auth repository should be used to write data or fetch data from an api or local storage;
import 'package:dio/dio.dart';
import 'package:glowing/utils/api_utils.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
abstract class AuthRepository {
Future<void> login({required String userName, required String password});
Future<void> forgotPassword();
}
class AuthRepositoryImpl implements AuthRepository {
#override
Future<void> forgotPassword() {
// TODO: implement forgotPassword
throw UnimplementedError();
}
#override
Future<void> login(
{required String userName, required String password}) async {
const String endpoint = "https://example.com/v1/login";
Map<dynamic, dynamic> params = {'email': userName, 'password': password};
Response response = await APIUtils.post(endpoint, params);
print(response);
if (response.statusCode == 200) {
//just printing response now
print(response.data);
}
}
}
final authRepositoryProvider =
StateProvider.autoDispose<AuthRepository>((ref) => AuthRepositoryImpl());

Future Provider Stuck In loading state

I am using a future provider to display a login page on load and then a loading indicator on loading. Here is my future provider
final loginProvider = FutureProvider.family((ref, UserInput input) =>
ref.read(authRepositoryProvider).doLogin(input.email, input.password));
In my UI I have this....
class LoginScreen extends HookWidget {
final TextEditingController emailEditingController = TextEditingController();
final TextEditingController passwordEditingController =
TextEditingController();
#override
Widget build(BuildContext context) {
var userInput =
UserInput(emailEditingController.text, passwordEditingController.text);
final login = useProvider(loginProvider(userInput));
return login.when(
data: (user) => Login(emailEditingController, passwordEditingController),
loading: () => const ProgressIndication(),
error: (error, stack) {
if (error is DioError) {
return Login(emailEditingController, passwordEditingController);
} else {
return Login(emailEditingController, passwordEditingController);
}
},
);
}
}
here is my doLogin function.
#override
Future<dynamic> doLogin(String email, String password) async {
try {
final response = await _read(dioProvider)
.post('$baseUrl/login', data: {'email': email, 'password': password});
final data = Map<String, dynamic>.from(response.data);
return data;
} on DioError catch (e) {
return BadRequestException(e.error);
} on SocketException {
return 'No Internet Connection';
}
}
I would like to know why it's stuck in the loading state. Any help will be appreciated.
First off, family creates a new instance of the provider when given input. So in your implementation, any time your text fields change, you're generating a new provider and watching that new provider. This is bad.
In your case, keeping the UserInput around for the sake of accessing the login state doesn't make a lot of sense. That is to say, in this instance, a FamilyProvider isn't ideal.
The following is an example of how you could choose to write it. This is not the only way you could write it. It is probably easier to grasp than streaming without an API like Firebase that handles most of that for you.
First, a StateNotifierProvider:
enum LoginState { loggedOut, loading, loggedIn, error }
class LoginStateNotifier extends StateNotifier<LoginState> {
LoginStateNotifier(this._read) : super(LoginState.loggedOut);
final Reader _read;
late final Map<String, dynamic> _user;
static final provider =
StateNotifierProvider<LoginStateNotifier, LoginState>((ref) => LoginStateNotifier(ref.read));
Future<void> login(String email, String password) async {
state = LoginState.loading;
try {
_user = await _read(authRepositoryProvider).doLogin(email, password);
state = LoginState.loggedIn;
} catch (e) {
state = LoginState.error;
}
}
Map<String, dynamic> get user => _user;
}
This allows us to have manual control over the state of the login process. It's not the most elegant, but practically, it works.
Next, a login screen. This is as barebones as they get. Ignore the error parameter for now - it will be cleared up in a moment.
class LoginScreen extends HookWidget {
const LoginScreen({Key? key, this.error = false}) : super(key: key);
final bool error;
#override
Widget build(BuildContext context) {
final emailController = useTextEditingController();
final passwordController = useTextEditingController();
return Column(
children: [
TextField(
controller: emailController,
),
TextField(
controller: passwordController,
),
ElevatedButton(
onPressed: () async {
await context.read(LoginStateNotifier.provider.notifier).login(
emailController.text,
passwordController.text,
);
},
child: Text('Login'),
),
if (error) Text('Error signing in'),
],
);
}
}
You'll notice we can use the useTextEditingController hook which will handle disposing of those, as well. You can also see the call to login through the StateNotifier.
Last but not least, we need to do something with our fancy new state.
class AuthPage extends HookWidget {
const AuthPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final loginState = useProvider(LoginStateNotifier.provider);
switch (loginState) {
case LoginState.loggedOut:
return LoginScreen();
case LoginState.loading:
return LoadingPage();
case LoginState.loggedIn:
return HomePage();
case LoginState.error:
return LoginScreen(error: true);
}
}
}
In practice, you're going to want to wrap this in another widget with a Scaffold.
I know this isn't exactly what you asked, but thought it might be helpful to see another approach to the problem.

Data not being updated after change the placeID in flutter BLoC

I am working on one project with BLoC.I have made some classes to update the data.
Data will come once you pass the placeID.
But once you pass the PlaceID first time it will give the data and widgets updated.
But once I pass new placeID old data not being updated. It shows old data.
All Code Files:
RestaurantDetailBloc.dart
class RestaurantDetailBloc extends Bloc<RestaurantDetailEvent, RestaurantDetailState> {
static final RestaurantDetailBloc _restaurantDetailBlocSingleton = new RestaurantDetailBloc._internal();
factory RestaurantDetailBloc() {
return _restaurantDetailBlocSingleton;
}
RestaurantDetailBloc._internal();
RestaurantDetailState get initialState => new UnRestaurantDetailState();
#override
Stream<RestaurantDetailState> mapEventToState(
RestaurantDetailEvent event,
) async* {
try {
yield await event.applyAsync(currentState: currentState, bloc: this);
} catch (_, stackTrace) {
print('$_ $stackTrace');
yield currentState;
}
}
}
LoadRestaurantDetailEvent.dart
#immutable
abstract class RestaurantDetailEvent {
Future<RestaurantDetailState> applyAsync(
{RestaurantDetailState currentState, RestaurantDetailBloc bloc});
final RestaurantDetailProvider _provider = RestaurantDetailProvider();
}
class LoadRestaurantDetailEvent extends RestaurantDetailEvent {
#override
String toString() => 'LoadRestaurantDetailEvent';
String placeID;
LoadRestaurantDetailEvent({Key key,this.placeID});
#override
Future<RestaurantDetailState> applyAsync(
{RestaurantDetailState currentState, RestaurantDetailBloc bloc}) async {
try {
await Future.delayed(new Duration(seconds: 2));
var component = await _provider.getRestaurantReview(placeID);
print(component);
return new InRestaurantDetailState(component);
} catch (_, stackTrace) {
print('$_ $stackTrace');
return new ErrorRestaurantDetailState(_?.toString());
}
}
}
RestaurantDetailPage.dart
class RestaurantDetailPage extends StatelessWidget {
static const String routeName = "/restaurantDetail";
final String imageURL;
final String placeID;
const RestaurantDetailPage({Key key, this.imageURL,this.placeID}) : super(key: key);
#override
Widget build(BuildContext context) {
var _restaurantDetailBloc = new RestaurantDetailBloc();
return new RestaurantDetailScreen(restaurantDetailBloc: _restaurantDetailBloc,imageUrl: this.imageURL,placeId: this.placeID,);
}
}
RestaurantDetailProvider.dart
class RestaurantDetailProvider {
String getBaseUrl(String placeID){
final urlBase = "https://maps.googleapis.com/maps/api/place/details/json?placeid=$placeID&key=xxxxxxxxxxxxxGooglePlaceKey";
return urlBase;
}
Future<void> loadAsync(String token) async {
/// write from keystore/keychain
await Future.delayed(new Duration(seconds: 2));
}
Future<void> saveAsync(String token) async {
/// write from keystore/keychain
await Future.delayed(new Duration(seconds: 2));
}
Future<Map<String, dynamic>> getRestaurantReview(String placeId)async{
var response = await http.get(getBaseUrl(placeId));
RestaurantReviews reviews = RestaurantReviews();
if(response.statusCode == 200){
var decodedJson = jsonDecode(response.body);
print(decodedJson);
//reviews.result = decodedJson['result'];
return decodedJson;
}
else{
}
}
}
InRestaurantDetailState.dart
#immutable
abstract class RestaurantDetailState extends Equatable {
RestaurantDetailState([Iterable props]) : super(props);
/// Copy object for use in action
RestaurantDetailState getStateCopy();
}
/// UnInitialized
class UnRestaurantDetailState extends RestaurantDetailState {
#override
String toString() => 'UnRestaurantDetailState';
#override
RestaurantDetailState getStateCopy() {
return UnRestaurantDetailState();
}
}
class InRestaurantDetailState extends RestaurantDetailState {
final resReview;
InRestaurantDetailState(this.resReview);
#override
String toString() => 'InRestaurantDetailState';
#override
RestaurantDetailState getStateCopy() {
return InRestaurantDetailState(resReview);
}
}
class ErrorRestaurantDetailState extends RestaurantDetailState {
final String errorMessage;
ErrorRestaurantDetailState(this.errorMessage);
#override
String toString() => 'ErrorRestaurantDetailState';
#override
RestaurantDetailState getStateCopy() {
return ErrorRestaurantDetailState(this.errorMessage);
}
}
RestaurantDetailScreenState.dart
class RestaurantDetailScreen extends StatefulWidget {
const RestaurantDetailScreen({
Key key,
#required RestaurantDetailBloc restaurantDetailBloc,
this.imageUrl, this.placeId,
}) : _restaurantDetailBloc = restaurantDetailBloc,
super(key: key);
final RestaurantDetailBloc _restaurantDetailBloc;
final String imageUrl;
final String placeId;
#override
RestaurantDetailScreenState createState() {
return new RestaurantDetailScreenState(_restaurantDetailBloc, imageUrl,placeId);
}
}
class RestaurantDetailScreenState extends State<RestaurantDetailScreen> {
final RestaurantDetailBloc _restaurantDetailBloc;
final String imageUrl;
final String placeId;
RestaurantDetailScreenState(this._restaurantDetailBloc, this.imageUrl,this.placeId);
#override
void initState() {
super.initState();
this._restaurantDetailBloc.dispatch(LoadRestaurantDetailEvent(placeID:placeId));
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark);
final width = MediaQuery.of(context).size.width;
final height = MediaQuery.of(context).size.height;
return BlocBuilder<RestaurantDetailBloc, RestaurantDetailState>(
bloc: widget._restaurantDetailBloc,
builder: (
BuildContext context,
var currentState,
) {
if (currentState is UnRestaurantDetailState) {
return MaterialApp(
home: new Scaffold(
body: new Container(
color: Colors.white,
child: Center(
child: CircularProgressIndicator(),
),
),
));
}
if (currentState is ErrorRestaurantDetailState) {
return new Container(
child: new Center(
child: new Text(currentState.errorMessage ?? 'Error'),
));
}
if (currentState is InRestaurantDetailState) {
var resList = currentState.resReview;
print(resList);
return MaterialApp(
home: new Scaffold(
)
);
}
Please help me guys.I have spent whole day.
Thank you in advance.
You need to pass the data to the parent class for comparison. That's why we are using equatable. Do these changes and it should work. Let me know if it doesn’t.
class InRestaurantDetailState extends RestaurantDetailState {
final resReview;
//You need to change this line to
InRestaurantDetailState(this.resReview):super([resReview]);
#override
String toString() => 'InRestaurantDetailState';
#override
RestaurantDetailState getStateCopy() {
return InRestaurantDetailState(resReview);
}
}