Dart: How to properly dispatch bloc event in another bloc - dart

I need to access AuthenticationBloc in my LoginBloc so I can fire the AuthenticationLogin() event if the login is successful. What I did so far is not working.
What I've done:
class LoginBloc extends Bloc<LoginEvent, LoginState> {
final AuthenticationBloc authenticationBloc;
final AuthenticateCredentialsUsecase authenticateCredentialsUsecase;
//code
Stream<LoginState> mapEventToState(
LoginEvent event,
) async* {
//code
authenticationBloc.add(AuthenticationLogin());
yield LoginLoadSuccess();
//code
}
}
What I'm trying to accomplish:
class _AppViewState extends State<AppView> {
final _navigatorKey = GlobalKey<NavigatorState>();
NavigatorState get _navigator => _navigatorKey.currentState;
#override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: _navigatorKey,
builder: (context, child) {
return BlocListener<AuthenticationBloc, AuthenticationState>(
listener: (context, state) {
if (state is AuthenticationAuthenticated) {
_navigator.pushAndRemoveUntil<void>(
HomePage.route(),
(route) => false,
);
}
else if (state is AuthenticationUnauthenticated){
_navigator.pushAndRemoveUntil<void>(
LoginScreen.route(),
(route) => false,
);
}
},
child: child,
);
},
onGenerateRoute: (_) => SplashPage.route(),
);
}
}
As you can see, the user is currently in the LoginScreen, once the login is successful, I need to yield the AuthenticationAuthenticated() state in my AuthenticationBloc() so my users will be directed to the HomePage()
How can I yield the AuthenticationAuthenticated() state of the AuthenticationBloc() inside my LoginBloc() - since my login logic happens inside the LoginBloc.

I subscribed the AuthenticationBloc to the status stream of my AuthenticateCredentialsUsecase class.
When the AuthenticateCredentialsUsecase is called in my LoginBloc and the credentials are authenticated...
I then update the status stream - _controller.add(AuthenticationStatus.authenticated);
Which inturn will trigger the AuthenticationLogin event
inside the AuthenticationBloc
AuthenticationBloc
AuthenticationBloc({
#required CheckAuthenticationStatusUsecase checkAuthenticationStatus,
#required LogoutAuthenticatedUserUsecase logoutAuthenticatedUser,
#required AuthenticateCredentialsUsecase authenticateCredentials,
}) : assert(checkAuthenticationStatus != null),
assert(logoutAuthenticatedUser != null),
assert(authenticateCredentials != null),
checkAuthenticationStatusUsecase = checkAuthenticationStatus,
logoutAuthenticatedUserUsecase = logoutAuthenticatedUser,
authenticateCredentialsUsecase = authenticateCredentials,
super(AuthenticationInitial()) {
add(AuthenticationStatusRequested());
_loginStatusSubscription =
authenticateCredentialsUsecase.status.listen((event) {
if (event == AuthenticationStatus.authenticated) {
add(AuthenticationLogin());
}
});
}
AuthenticateCredentialsUsecase
final _controller = StreamController<AuthenticationStatus>();
Stream<AuthenticationStatus> get status async* {
yield AuthenticationStatus.unknown;
yield* _controller.stream;
}
void dispose() => _controller.close();
#override
Future<Either<Failure, AuthenticatedUser>> call(AuthenticationParams params) async {
final result = await repository.authenticateCredentials(params.userName, params.password);
if(result is Right){
_controller.add(AuthenticationStatus.authenticated);
}
return result;
}

Here is one way to do it. You must call BlocBuilder to handles building the widget in response to new states.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
debugShowCheckedModeBanner: false,
home: BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (context, state) {
//If the login is successful, show homepage
if (state is AuthenticationAuthenticated) {
return HomePage();
}
//If the login failed, show login screen
if (state is AuthenticationUnauthenticated) {
return LoginScreen();
}
//If the login is in process, show loading indicator
if (state is AuthenticationInProgress) {
return LoadingIndicator();
}
return SplashScreen();
},
),
);
}
}
At first, the state is AuthenticationUnauthenticated and displays the login screen. If the login is successful then we display homepage, otherwise if it failed we will display LoginScreen.
class LoginBloc extends Bloc<LoginEvent, LoginState> {
final AuthenticationBloc authenticationBloc;
final AuthenticateCredentialsUsecase authenticateCredentialsUsecase;
//code
Stream<LoginState> mapEventToState(
LoginEvent event,
) async* {
if(event is LoginButtonPressed) {
// some logic code
// eg. : final response = UserRepository.login(username: event.username, password: event.password);
authenticationBloc.add(AuthenticationLogin());
//code
}
}
}
And here is the AuthenticationBloc () code which will handle the authentication.
class AuthenticationBloc extends Bloc<AuthenticationEvent, AuthenticationState>{
//code
Stream<AuthenticationState> mapEventToState(
AuthenticationEvent event,
) async* {
if(event is AuthenticationLogin) {
yield AuthenticationInProgress();
//code
yield AuthenticationAuthenticated();
}
}
}

Related

Flutter integrating Hive database with Riverpod

There is very easy way to use Hive key-value database on StatefulWidgets, for example:
class HookDemo extends StatefulWidget {
#override
_HookDemoState createState() => _HookDemoState();
}
class _HookDemoState extends State<HookDemo> {
Box user;
#override
void initState() {
super.initState();
user = Hive.box<User>('user');
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
final _u = User()
..nameFamily = 'myname'
..mobileNumber = '123456789';
_user!.add(_u);
_u.save();
},
child: Icon(Icons.add),
),
...
);
}
}
here we defined Box user property and inside initState we implemented what's user such as user = Hive.box<User>('user');
after that we can use user without any problem and getting already opened error
now in this current application we used HookWidget and when we want to use Hive we get error as box already opened
main.dart:
Future<void> initHiveDriver() async {
final appDocumentDirectory = await path_provider.getApplicationDocumentsDirectory();
await Hive.initFlutter(appDocumentDirectory.path);
await Hive.openBox<UserAdapter>('user');
}
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
///...
initHiveDriver();
runApp(
ProviderScope(observers: [
Logger()
],
child: MyApp()),
);
}
how can i create a provider for Hive with Riverpod and use it into HookWidget?
I am using Hive with Riverpod like this.
I am using a named constructor so I can await the openBox call.
final hiveProvider = FutureProvider<HiveDB>((_) => HiveDB.create());
class HiveDB {
var _userBox;
HiveDB._create() {}
static Future<HiveDB> create() async {
final component = HiveDB._create();
await component._init();
return component;
}
_init() async {
Hive.registerAdapter(UserAdapter());
this._userBox = await Hive.openBox<User>('user');
}
storeUser(User user) {
this._userBox.put('user', user);
}
User getUser() {
return this._userBox.get('user');
}
}
Use in a ConsumerWidget:
class SomeWidget extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final provider = ref.watch(hiveProvider).data?.value;
...
}
}

Flutter: Asking Location permission pauses all app execution, how to let it run asynchronously

When my app requests location permission the entire app stops until the dialog is complete (e.g. if the permission dialog pops up during a page transition the transition will freeze mid transition until the dialog is resolved).
Literally it causes execution to pause.
Using: flutter_riverpod, location.
The offending code:
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter_riverpod/all.dart';
import 'package:location/location.dart';
class MapApiBloc extends StateNotifier<MapApiState> {
// Instantiating a location service
static Location _location = Location();
// This will subscribe to the user's location
StreamSubscription<LocationData> _streamSubscription;
// Permissions stuff
bool _serviceEnabled;
PermissionStatus _permissionGranted;
// Initial (empty) state
static const MapApiState _initialState = MapApiState(userLocation: null);
MapApiBloc() : super(_initialState) {
init();
}
// This runs when you initialize the class
init() async {
// Checks if user toggled on location service
_serviceEnabled = await _location.serviceEnabled();
if (!_serviceEnabled) {
_serviceEnabled = await _location.requestService();
if (!_serviceEnabled) {
return;
}
}
// Asks for permission
_permissionGranted = await _location.hasPermission();
if (_permissionGranted == PermissionStatus.denied) {
_permissionGranted = await _location.requestPermission();
if (_permissionGranted != PermissionStatus.granted) {
return;
}
}
// Starts the subscription
_streamSubscription = _location.onLocationChanged.listen((event) {
state = MapApiState(userLocation: event);
});
}
#override
void dispose() {
_streamSubscription.cancel();
super.dispose();
}
}
class MapApiState {
final LocationData userLocation;
const MapApiState({#required this.userLocation});
}
final mapApiProvider = StateNotifierProvider<MapApiBloc>((ref) {
return MapApiBloc();
});
UI Code:
class ViewNearbyMapPage extends StatefulWidget {
#override
_ViewNearbyMapPageState createState() => _ViewNearbyMapPageState();
}
class _ViewNearbyMapPageState extends State<ViewNearbyMapPage> {
Completer<GoogleMapController> _controller = Completer();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Search Sellers")),
body: Consumer(
builder: (context, watch, child) {
var location = watch(mapApiProvider.state);
if (location.userLocation?.latitude == null) {
return Center(child: CircularProgressIndicator());
}
CameraPosition _myPosition = CameraPosition(
target: LatLng(location.userLocation.latitude,
location.userLocation.longitude),
zoom: 14.4746,
);
return GoogleMap(
initialCameraPosition: _myPosition,
onMapCreated: (controller) {
_controller.complete(controller);
},
);
},
),
);
}
}

How to continuously check internet connect or not on Flutter?

I use this code for check internet. and I wrap this function into initState also. Snack bar always displays when internet not available. But after connecting to the internet, the snack bar is not disappeared. I can't use connectivity plugin because they said on Android, the plugin does not guarantee connection to the Internet.
checking1(TextEditingController usernameController, BuildContext context,
String _url, GlobalKey<ScaffoldState> _scaffoldKey) async {
try {
final result = await InternetAddress.lookup('google.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
usernameController.text == '' ?
showDialog(...some code...) :
usernameValidation(usernameController.text, context, _url);
}
}
on SocketException
catch (_) {
_showSnackBar(_scaffoldKey);
}
}
Full example demonstrating a listener of the internet connectivity and its source.
Original post
import 'dart:async';
import 'dart:io';
import 'package:connectivity/connectivity.dart';
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
Map _source = {ConnectivityResult.none: false};
MyConnectivity _connectivity = MyConnectivity.instance;
#override
void initState() {
super.initState();
_connectivity.initialise();
_connectivity.myStream.listen((source) {
setState(() => _source = source);
});
}
#override
Widget build(BuildContext context) {
String status = "Offline";
switch (_source.keys.toList()[0]) {
case ConnectivityResult.none:
status = "Offline";
break;
case ConnectivityResult.mobile:
status = "Mobile: Online";
break;
case ConnectivityResult.wifi:
status = "WiFi: Online";
break;
case ConnectivityResult.ethernet:
status = "Ethernet: Online";
break;
}
return Scaffold(
appBar: AppBar(title: Text("Internet")),
body: Center(child: Text(status)),
);
}
#override
void dispose() {
_connectivity.disposeStream();
super.dispose();
}
}
class MyConnectivity {
MyConnectivity._internal();
static final MyConnectivity _instance = MyConnectivity._internal();
static MyConnectivity get instance => _instance;
Connectivity connectivity = Connectivity();
StreamController controller = StreamController.broadcast();
Stream get myStream => controller.stream;
void initialise() async {
ConnectivityResult result = await connectivity.checkConnectivity();
_checkStatus(result);
connectivity.onConnectivityChanged.listen((result) {
_checkStatus(result);
});
}
void _checkStatus(ConnectivityResult result) async {
bool isOnline = false;
try {
final result = await InternetAddress.lookup('example.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
isOnline = true;
} else {
isOnline = false;
}
} on SocketException catch (_) {
isOnline = false;
}
controller.sink.add({result: isOnline});
}
void disposeStream() => controller.close();
}
Another option also can be this package: https://pub.dartlang.org/packages/flutter_offline that deal with this issue really straightforward.
You need first to import the package 'package:flutter_offline/flutter_offline.dart';
After that you include the OfflineBuilder on Widget build(BuildContext context) and it will read all all stream changes from ConnectivityResult continuously.
Like the example on the link or like the following one
#override
Widget build(BuildContext context) {
return OfflineBuilder(
debounceDuration: Duration.zero,
connectivityBuilder: (
BuildContext context,
ConnectivityResult connectivity,
Widget child,
) {
if (connectivity == ConnectivityResult.none) {
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: Center(child: Text('Please check your internet connection!')),
);
}
return child;
},
child: Scaffold(
resizeToAvoidBottomPadding: false,
appBar: AppBar(
title: Text("Home")
),
body: new Column(
children: <Widget>[
new Container(
decoration: new BoxDecoration(color: Theme.of(context).cardColor),
child: _buildTxtSearchBox(),
),
new Divider(height: 10.0),
new FloatingActionButton.extended(
icon: Icon(Icons.camera_alt),
),
new Container(
...
),
],
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
drawer: MenuDrawer(),
)
);
}
The connectivity package will do what you want. It has an onConnectivityChanged stream which you can subscribe to. This will notify your app when the connectivity state changes. But just because your device is connected to a network doesn't mean it can access your server and be connected. So a DNS lookup would be a good idea before then updating the internal state of your application.
https://pub.dartlang.org/documentation/connectivity/latest/connectivity/Connectivity-class.html
I find this to be reliable & more convincing :
Future<bool> connectivityChecker() async {
var connected = false;
print("Checking internet...");
try {
final result = await InternetAddress.lookup('google.com');
final result2 = await InternetAddress.lookup('facebook.com');
final result3 = await InternetAddress.lookup('microsoft.com');
if ((result.isNotEmpty && result[0].rawAddress.isNotEmpty) ||
(result2.isNotEmpty && result2[0].rawAddress.isNotEmpty) ||
(result3.isNotEmpty && result3[0].rawAddress.isNotEmpty)) {
print('connected..');
connected = true;
} else {
print("not connected from else..");
connected = false;
}
} on SocketException catch (_) {
print('not connected...');
connected = false;
}
return connected;
}
Based on the bool value of connected returned, I'd run a timer based loop to check for internet again & again till its connected. Open to any suggestions

Flutter set startup page based on Shared Preference

I've been trying without success to load different pages according to my Shared Preference settings.
Based on several posts found in stackoverflow, i end up with the following solution:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:testing/screens/login.dart';
import 'package:testing/screens/home.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Widget page = Login();
Future getSharedPrefs() async {
String user = Preferences.local.getString('user');
if (user != null) {
print(user);
this.page = Home();
}
}
#override
void initState() {
super.initState();
this.getSharedPrefs();
}
#override
Widget build(BuildContext context) {
return MaterialApp(home: this.page);
}
}
class Preferences {
static SharedPreferences local;
/// Initializes the Shared Preferences and sets the info towards a global variable
static Future init() async {
local = await SharedPreferences.getInstance();
}
}
The variable user is not null because the print(user) returns a value as expected, but the login screen is always being opened.
Your problem is that your build method returns before your getSharedPrefs future is complete. The getSharedPrefs returns instantly as soon as it's called because it's async and you're treating it as a "Fire and Forget" by not awaiting. Seeing that you can't await in your initState function that makes sense.
This is where you want to use the FutureBuilder widget. Create a Future that returns a boolean (or enum if you want more states) and use a future builder as your home child to return the correct widget.
Create your future
Future<bool> showLoginPage() async {
var sharedPreferences = await SharedPreferences.getInstance();
// sharedPreferences.setString('user', 'hasuser');
String user = sharedPreferences.getString('user');
return user == null;
}
When user is null this will return true. Use this future in a Future builder to listen to the value changes and respond accordingly.
#override
Widget build(BuildContext context) {
return MaterialApp(home: FutureBuilder<bool>(
future: showLoginPage(),
builder: (buildContext, snapshot) {
if(snapshot.hasData) {
if(snapshot.data){
// Return your login here
return Container(color: Colors.blue);
}
// Return your home here
return Container(color: Colors.red);
} else {
// Return loading screen while reading preferences
return Center(child: CircularProgressIndicator());
}
},
));
}
I ran this code and it works fine. You should see a blue screen when login is required and a red screen when there's a user present. Uncomment the line in showLoginPage to test.
There is a much pretty way of doing this.
Assuming that you have some routes and a boolean SharedPreference key called initialized.
You need to use the WidgetsFlutterBinding.ensureInitialized() function before calling runApp() method.
void main() async {
var mapp;
var routes = <String, WidgetBuilder>{
'/initialize': (BuildContext context) => Initialize(),
'/register': (BuildContext context) => Register(),
'/home': (BuildContext context) => Home(),
};
print("Initializing.");
WidgetsFlutterBinding.ensureInitialized();
await SharedPreferencesClass.restore("initialized").then((value) {
if (value) {
mapp = MaterialApp(
debugShowCheckedModeBanner: false,
title: 'AppName',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: routes,
home: Home(),
);
} else {
mapp = MaterialApp(
debugShowCheckedModeBanner: false,
title: 'AppName',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes: routes,
home: Initialize(),
);
}
});
print("Done.");
runApp(mapp);
}
The SharedPreference Class Code :
class SharedPreferencesClass {
static Future restore(String key) async {
final SharedPreferences sharedPrefs = await SharedPreferences.getInstance();
return (sharedPrefs.get(key) ?? false);
}
static save(String key, dynamic value) async {
final SharedPreferences sharedPrefs = await SharedPreferences.getInstance();
if (value is bool) {
sharedPrefs.setBool(key, value);
} else if (value is String) {
sharedPrefs.setString(key, value);
} else if (value is int) {
sharedPrefs.setInt(key, value);
} else if (value is double) {
sharedPrefs.setDouble(key, value);
} else if (value is List<String>) {
sharedPrefs.setStringList(key, value);
}
}
}

Flutter: inherited widget

I want to do a login app. I have a class user, which has an id and a username that I want to keep to display it later in the app, and I have a user_api class, where I do the http request.
I wanted to use Singleton to store the user once the user logins in, but I find out that inherited widget was a better idea. So now I'm struggling with them because I can't store the user object. After I login, my user becomes null and I can't figure out how it works. Here's my code: basically we have a root page that manages the cases in which the user is logged in or not:
void main() {
runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Model(
user: User(),
child: MaterialApp(
routes: {
'/login': (context) => new LoginView(),
'/homepage_view': (context) => new HomepageView(),
},
title: 'Flutter login demo',
home: RootPage(),
),
);
}
}
In the rootPage:
enum UserStatus {
notDetermined,
notSignedIn,
signedIn,
}
class RootPage extends StatefulWidget {
#override
_RootPageState createState() => _RootPageState();
}
class _RootPageState extends State<RootPage> {
UserStatus userStatus = UserStatus.notDetermined;
#override
void didChangeDependencies() {
// TODO: implement didChangeDependencies
super.didChangeDependencies();
var user = Model.of(context).user;
setState(() {
userStatus = user.id == null? UserStatus.notSignedIn : UserStatus.signedIn;
print((userStatus));
});
}
void _signedIn() {
setState(() {
userStatus = UserStatus.signedIn;
});
}
void _signedOut() {
setState(() {
userStatus = UserStatus.notSignedIn;
});
}
#override
Widget build(BuildContext context) {
switch (userStatus) {
case UserStatus.notDetermined:
return _buildWaitingScreen();
case UserStatus.notSignedIn:
return LoginView(
onSignedIn: _signedIn,
);
case UserStatus.signedIn:
return HomepageView(
onSignedOut: _signedOut,
);
}
return Container(
child: Text(("CHILD")),
);
}
}
Widget _buildWaitingScreen() {
return Scaffold(
body: Container(
alignment: Alignment.center,
child: CircularProgressIndicator(),
),
);
}
the most important stuff of the login page::
var user = Model.of(context).user;
user = await getUserByIdClient()
if (user.loginError == false){
print (user);
widget.onSignedIn();
}
Here's my inherited widget:
class Model extends InheritedWidget {
Model({Key key, Widget child, this.user}) : super(key: key, child: child);
final User user;
#override
bool updateShouldNotify(InheritedWidget oldWidget) => true;
static Model of(BuildContext context) {
return (context.inheritFromWidgetOfExactType(Model) as Model);
}
}
From what I understand, it seems that you're looking in to storing user session in your app. One way of doing this is by storing user credentials in shared_preferences (i.e. userId). Depending on your use case, your backend might require users to re-authenticate, so keep an eye on that.
Store user credentials after login.
// Obtain shared preferences.
final prefs = await SharedPreferences.getInstance();
// Save user details on userId
String userId = ...;
await prefs.setString('userId', userId);
When has been signed out, you can remove the data.
await prefs.remove('userId');
For verifying user session, you can then check the stored value. If it's empty, logout the user.
final String? userId = prefs.getString('userId');
if(userId != null){
// User is logged-in
} else {
// User is signed-out
}

Resources