Flutter + Hive Check if value is present in box in Future Builder - flutter

I am crafting a new app and saving the response from a REST API call to a Hive box, which is successful (as far as I know). What I am trying to do in my main.dart is check if the value for token is set in Hive and if it is load an alternative view other than the login view.
My main.dart
import 'package:flutter/material.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:path_provider/path_provider.dart' as path_provider;
import 'package:myapp/model/user_model.dart';
import 'package:myapp/views/login_view.dart';
import 'package:myapp/views/project_view.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final appDocsDir = await path_provider.getApplicationDocumentsDirectory();
Hive.init(appDocsDir.path);
Hive.registerAdapter(UserModelAdapter());
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
State<PocketPolarix> createState() => _PocketPolarixState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
final user = Hive.box('user');
return MaterialApp(
title: 'Startup Name Generator',
theme: ThemeData(
appBarTheme: const AppBarTheme(
backgroundColor: Color(0xFF2036B8),
foregroundColor: Colors.white,
),
),
home: FutureBuilder(
future: Hive.openBox('user'),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return Text(snapshot.error.toString());
} else {
Hive.openBox('user');
if (user.get('token') != null) {
return const ProjectView();
} else {
return const LoginView();
}
}
} else {
return Scaffold();
}
},
),
);
}
#override
void dispose() {
Hive.close();
super.dispose();
}
The code is failing at the second if statement where I check to see if Hive.box('user').get('token') != null what I keep getting is the following error.
throw HiveError('Box not found. Did you forget to call Hive.openBox()?'); as you can see from the code however, I am opening the box.
I am new to Dart and Flutter and Hive for that matter so a helping hand here would be great, thanks!

Part 1 of the issue is that you were trying to access user before opening. Secondly, I believe you need to check if the snapshot.hasData before proceeding with any operation i.e.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Startup Name Generator',
theme: ThemeData(
appBarTheme: const AppBarTheme(
backgroundColor: Color(0xFF2036B8),
foregroundColor: Colors.white,
),
),
home: FutureBuilder(
future: Hive.openBox<YourUserModelBox>('user'),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return Text(snapshot.error.toString());
} else if (snapshot.hasData) {
if (snapshot.data is YourUserModelBox) {
final user = snapshot.data as YourUserModelBox;
if (user.get('token') != null) {
return const ProjectView();
} else {
return const LoginView();
}
} else {
Scaffold();
}
}
} else {
return Scaffold();
}
},
),
);
}

The problem is with the first line of code in your build function. You're trying to get the user box from Hive without opening it first. Here is what can you do instead:
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Startup Name Generator',
theme: ThemeData(
appBarTheme: const AppBarTheme(
backgroundColor: Color(0xFF2036B8),
foregroundColor: Colors.white,
),
),
home: FutureBuilder<Box>(
future: Hive.openBox('user'),
builder: (BuildContext context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return Text(snapshot.error.toString());
} else {
if (snapshot.data?.get('token') != null) {
return const ProjectView();
} else {
return const LoginView();
}
}
} else {
return Scaffold();
}
},
),
);
}

Related

Why can i see black screen when using BlocBuilder though i have taken all possible measures?

This is my main file, I am trying to check for internet connection.And showing dialog if there is no internet connection using flutter cubit.
But the only hurdle is for a flicker of second the screen goes black and then dialog is displayed , how can i avoid this?
main.file
void main() {
runApp(BlocProvider(
create: (BuildContext context) => ConnectivityCubit()..checkConnectivity(),
lazy: false,
child: MaterialApp(home: MyApp()),
));
}
class MyApp extends StatelessWidget {
MyApp({super.key});
bool _isDialogDisplayed = false;
#override
Widget build(BuildContext context) {
return BlocConsumer<ConnectivityCubit, ConnectivityState>(
listener: (context, state) {
if (state == ConnectivityState.disconnected) {
_isDialogDisplayed = true;
showDialog(
context: context,
builder: (context) => const AlertDialog(
title: Text('No Internet'),
content: Text('Please check your internet connection.'),
),
);
}
if (state == ConnectivityState.connected &&
_isDialogDisplayed == true) {
Navigator.of(context).pop();
_isDialogDisplayed = false;
}
},
builder: (context, state) {
if (state == ConnectivityState.init) {
return const CircularProgressIndicator();
}
return MaterialApp( // <-- This is causing problem
home: Scaffold(
body: state == ConnectivityState.connected
? const Center(
child: Text('Hello World'),
)
: const Center(child: CircularProgressIndicator()),
),
);
},
);
}
}
cubit.file
import 'dart:async';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
enum ConnectivityState { connected, disconnected, init }
class ConnectivityCubit extends Cubit<ConnectivityState> {
final Connectivity _connectivity = Connectivity();
StreamSubscription<ConnectivityResult>? _subscription;
late Stream<ConnectivityResult> streamValue;
ConnectivityCubit() : super(ConnectivityState.init) {
streamValue = _connectivity.onConnectivityChanged;
_subscription = _connectivity.onConnectivityChanged.listen((result) {
if (result == ConnectivityResult.none) {
emit(ConnectivityState.disconnected);
} else {
emit(ConnectivityState.connected);
}
});
}
checkConnectivity() async {
final result = await _connectivity.checkConnectivity();
if (result == ConnectivityResult.none) {
emit(ConnectivityState.disconnected);
} else {
emit(ConnectivityState.connected);
}
}
#override
Future<void> close() {
_subscription?.cancel();
return super.close();
}
}
I have tried to simply use this way
return const MaterialApp(
home: Scaffold(
body: Center(
child: Text('Hello World'),
)),
);
The above code solves black screen issue but it will show Hello World for fraction of second i.e because of the time taken to build dialog by the BlocListener. To overcome that I tried the above method. Though i have things wrapped inside the MaterialApp why do i see black screen?
you want builder part in check status and then showDialog()
MyApp({super.key});
bool _isDialogDisplayed = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: BlocConsumer<ConnectivityCubit, ConnectivityState>(
listener: (context, state) {
if (state == ConnectivityState.disconnected) {
_isDialogDisplayed = true;
showDialog(
context: context,
builder: (context) => const AlertDialog(
title: Text('No Internet'),
content: Text('Please check your internet connection.'),
),
);
}
if (state == ConnectivityState.connected &&
_isDialogDisplayed == true) {
Navigator.of(context).pop();
_isDialogDisplayed = false;
}
},
builder: (context, state) {
if (state == ConnectivityState.init) {
return const CircularProgressIndicator();
}
else if(state == ConnectivityState.disconnected){
_isDialogDisplayed = true;
showDialog(
context: context,
builder: (context) => const AlertDialog(
title: Text('No Internet'),
content: Text('Please check your internet connection.'),
),
);
}
},
);
);
}
}

Cant check if user account exists?

Helo, I am trying to check if user's account exists, if no, I want to run text 'account is deleted'.
But the problem is that when I start the app there is screen for existing account and only after reset I can get the real result.
Looks like check for account is done after running app for the first time, but I don't know where is the mistake.
Here is the code, thank you in advance:
class CheckIfDeletedAccount extends StatelessWidget {
String isAccountDeleted;
getData() async {
var userType = await Firestore.instance
.collection('users')
.where('userEmail', isEqualTo: email)
.getDocuments();
userType.documents.forEach((result) {
log(result.data["deleted"]);
isAccountDeleted = result.data["deleted"].toString();
});
}
#override
Widget build(BuildContext context) {
getData();
//log(isAccountDeleted);
if (isAccountDeleted == "true") {
return Scaffold(
body: Container(
child: Center(
child: Text("account is deleted"),
),
),
);
}
return MaterialApp(
theme: themeData,
home: Scaffold(
body: Bar(),
),
);
}
}
You need to wait for the result from Firebase. You are trying to build the widget before the isAccountDeleted is initialized.
In your scenario, you can use FutureBuilder as follows:
class CheckIfDeletedAccount extends StatelessWidget {
String isAccountDeleted;
Future<String> getData() async {
var userType = await Firestore.instance
.collection('users')
.where('userEmail', isEqualTo: email)
.getDocuments();
userType.documents.forEach((result) {
log(result.data["deleted"]);
isAccountDeleted = result.data["deleted"].toString();
});
return isAccountDeleted;
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: getData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if(snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
final isAccountDeleted = snapshot.data;
if (isAccountDeleted == "true") {
return Scaffold(
body: Container(
child: Center(
child: Text("account is deleted"),
),
),
);
}
return MaterialApp(
theme: themeData,
home: Scaffold(
body: Bar(),
),
);
}
return Center(child: const CircularProgressIndicator());
},
);
}
}
Based on savke comment you can use the following code using FutureBuilder:
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class CheckIfDeletedAccount extends StatelessWidget {
Future getData() async {
String isAccountDeleted;
var userType = await Firestore.instance
.collection('users')
.where('userEmail', isEqualTo: email)
.getDocuments();
userType.documents.forEach((result) {
log(result.data["deleted"]);
isAccountDeleted = result.data["deleted"].toString();
});
return isAccountDeleted;
}
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: themeData,
home: Scaffold(
body: FutureBuilder(
future: getData(),
builder: (context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
strokeWidth: 6,
valueColor: AlwaysStoppedAnimation<Color>(Colors.red),
),
);
} else {
if (snapshot.data == "true") {
return Container(
child: Center(
child: Text("account is deleted"),
),
);
}
else {
return Bar();
}
}
}),
));
}
}

Flutter: firebase authorization flow won't work with routes

I'm trying to make sure that app-users that aren't authorised (signed in), are directed to the sign in page. This is what my main.dart looks like:
class App extends StatelessWidget {
Widget build(BuildContext context) {
return StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.active) {
// some loading widget
return MaterialApp(home: Scaffold(),);
}
FirebaseUser user = snapshot.data;
if (user == null) {
return MaterialApp(home: SignIn(),);
}
// this is the main app
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => HomeScreen(),
'/new_game': (context) => NewGame(),
'/join_session': (context) => JoinSession(),
'/my_rankings': (context) => MyRankings(),
'/settings': (context) => Settings(),
},
);
}
);
}
}
When I run the app (on chrome using the web function) I start of at the sign in page (as expected) which just contains an anonymous sign in button. When I sign in it gives an error, stating that the initial-route-builder (route '/' with HomeScreen() as builder) returns null. When I swap my the main app for a simple
return MaterialApp(home: Scaffold(body: Text('This Works')));
it does seem to work. When using a simple MaterialApp() that does the same but using routes, it gives the error again, so the problem seems to be the routing. What's going on?
i don't think it will work like that. the main.dart should be used only as the app entry point.
class App extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => SplashScreen(),
'/home': (context) => HomeScreen(),
'/new_game': (context) => NewGame(),
'/join_session': (context) => JoinSession(),
'/my_rankings': (context) => MyRankings(),
'/settings': (context) => Settings(),
},
);
}
}
class SplashScreen extends StatelessWidget {
void isLogged StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.active) {
// some loading widget
Navigator.pushReplacementNamed(context, '/home');
}
FirebaseUser user = snapshot.data;
if (user == null) {
Navigator.pushReplacementNamed(context, '/SignIn');
}
// this is the main app
);
initiatState(){
isLogged();
}
Widget build(BuildContext context){
}
}
You could use this method to check if a user is logged auth.currentUser() it returns null if the user is not signed
FirebaseAuth auth = FirebaseAuth.instance;
await auth.currentUser() == null ? false : true;
Just do this!
import 'package:base_app/screens/auth/login/index.dart';
import 'package:base_app/screens/boarding/index.dart';
import 'package:base_app/screens/main/home/index.dart';
import 'package:base_app/screens/main/profile/index.dart';
import 'package:base_app/screens/splash/index.dart';
import 'package:base_app/style/palette.dart';
import 'package:base_app/style/theme.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:sizer/sizer.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Sizer(
builder: (context, orientation, deviceType) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
fontFamily: 'DancingScript',
textTheme: getTextTheme(context),
primarySwatch: colorRed as MaterialColor,
),
home: LandingFlow(),
routes: {
ProfileScreen.id: (context) => ProfileScreen(),
},
);
},
);
}
}
class LandingFlow extends StatefulWidget {
#override
_LandingFlowState createState() => _LandingFlowState();
}
class _LandingFlowState extends State<LandingFlow> {
bool isSplashOver = false;
bool hasBoardingScreensShown = true;
#override
Widget build(BuildContext context) {
if (isSplashOver) {
if (hasBoardingScreensShown) {
return StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
if (snapshot.hasData) {
debugPrint('HomeScreen');
return HomeScreen();
} else {
debugPrint('LoginScreen');
return LoginScreen();
}
},
);
}
return BoardingScreen();
}
return SplashScreen(
onFinished: () => setState(() {
isSplashOver = true;
}),
);
}
}
And for your Splash Screen:
import 'package:flutter/material.dart';
class SplashScreen extends StatelessWidget {
static const id = 'SplashScreen';
final Function onFinished;
const SplashScreen({this.onFinished});
#override
Widget build(BuildContext context) {
Future.delayed(
const Duration(seconds: 3),
() => onFinished(),
);
return const Scaffold(
body: Center(
child: Text('Splash Screen'),
),
);
}
}

Flutter Firebase Authentication: delay on startup

I am using Provider and the stream FirebaseAuth.instance.onAuthStateChanged in the app to decide where to redirect on startup, but although the user is already logged in (from a previous startup) the app starts on the login screen and almost 1 second later redirects to the home page, from which it should have started from the first moment. This happens even in airplane mode.
I would like to know if there is any approach to solve this, even if it is not possible to show the home screen at once, I don't know how to differentiate between the not logged user (null->login screen) and loading user (null->loading screen).
Some of the code:
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final FirebaseAuth _auth = FirebaseAuth.instance;
final DatabaseService db = DatabaseService();
#override
Widget build(BuildContext context) {
return StreamProvider<FirebaseUser>.value(
value: _auth.onAuthStateChanged,
child: Consumer<FirebaseUser>(
builder: (context, firebaseUser, child) {
return MultiProvider(
providers: [
if (firebaseUser != null)
ChangeNotifierProvider(create: (ctx) => CollectionState(firebaseUser)),
StreamProvider<List<Collection>>.value(value: db.streamCollections(firebaseUser)),
],
child: MaterialApp(
title: 'My App',
routes: {
'/': (ctx) => LandingPage(),
'/login': (ctx) => LoginPage(),
'/emailSignIn': (ctx) => EmailSignInPage(),
'/emailSignUp': (ctx) => EmailSignUpPage(),
'/emailUnverified': (ctx) => EmailUnverifiedPage(),
'/home': (ctx) => HomePage(),
'/settings': (ctx) => Settings(),
},
),
);
},
),
);
}
}
class LandingPage extends StatelessWidget {
final DatabaseService _db = DatabaseService();
#override
Widget build(BuildContext context) {
final user = Provider.of<FirebaseUser>(context);
final userCondition =
user == null ? 'null' : user.isEmailVerified ? 'verifiedUser' : 'unverifiedUser';
switch (userCondition) {
case 'null':
return LoginPage();
break;
case 'unverifiedUser':
return EmailUnverifiedPage();
break;
case 'verifiedUser':
return HomePage();
break;
}
}
}
The code is a bit simplified, I use a service for the authentication instance instead, just that.
I know I'm very late, but I've had the same problem for weeks and I finally figured it out.
#ChinkySight is right when he says it's best to use a StreamBuilder, mostly because you have access to the connectionState property.
The reason why lag exists is because the connection to the stream is not fully established. So during ConnectionState.waiting, return a widget like a splash screen or just a container.
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (_, snapshot) {
// Added this line
if (snapshot.connectionState == ConnectionState.waiting) {
return Container();
}
if (snapshot.data is FirebaseUser && snapshot.data != null) {
return HomePage();
}
return LoginPage();
});
}
}
You can even give your return statements fancy animations with the Animated Switcher
return StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
Widget widget;
if (snapshot.connectionState == ConnectionState.waiting) {
return Container();
}
switch (snapshot.hasData) {
case (true):
widget = HomePage();
break;
case (false):
widget = LoginPage();
}
return Stack(
children: <Widget>[
Scaffold(
backgroundColor: Colors.grey.shade200,
),
AnimatedSwitcher(
duration: Duration(milliseconds: 700),
child: FadeTransition(
opacity: animation,
child: widget,
),
);
},
)
],
);
},
);
This works for FlutterFire.
Firebase Auth enables you to subscribe in realtime to this state via a
Stream. Once called, the stream provides an immediate event of the
user's current authentication state, and then provides subsequent
events whenever the authentication state changes. To subscribe to
these changes, call the authStateChanges() method on your FirebaseAuth
instance:
import 'package:firebase_auth/firebase_auth.dart' as auth;
import 'package:flutter/material.dart';
import 'menu.dart';
import 'login.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:firebase_core/firebase_core.dart';
Future main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
MyApp()
);
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp, DeviceOrientation.portraitUp]);
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'TestApp',
theme: ThemeData(primarySwatch: Colors.blue),
home:
StreamBuilder<auth.User>(
stream: auth.FirebaseAuth.instance.authStateChanges(),
builder: (BuildContext context, AsyncSnapshot<auth.User> snapshot) {
if(snapshot.hasData) {
print("data exists");
return HomePage();
}
else {
return LoginPage();
}
},
)
);
}
}

FutureBuilder in flutter building app again and again?

what I am trying to do is check if user is logged in already or not. there is a futurebuilder inside consumer which is notifying listeners. check code:
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: Auth(),
)
],
child: Consumer<Auth>(
builder: (ctx, auth, _) {
print('making it again and again');
return MaterialApp(
title: 'MY app',
theme: ThemeData(
primarySwatch: Colors.blue,
accentColor: Colors.deepOrange,
),
home: auth.isAuth
? FirstScreen()
: FutureBuilder(
future: auth.tryAutoLogin(), //inside this function there is notifylisteners()
builder: (ctx, authResultSnapShot) =>
authResultSnapShot.connectionState ==
ConnectionState.waiting
? SplashScreen()
: AuthScreen(),
));
},
),
);
}
What I get:
this is rebuilding the app again and again. tryautologin is called repeatedly.
What I want:
check for is a user logged in only once the app is started.
Future<bool> tryAutoLogin() async {
final prefs = await SharedPreferences.getInstance();
if (!prefs.containsKey('userData')) {
return false;
}
final extractedUserData = json.decode(prefs.getString('userData'));
final expiryDate = DateTime.parse(extractedUserData['expiryDate']);
_accessToken = extractedUserData['accessToken'];
_refreshToken = extractedUserData['refreshToken'];
print(extractedUserData['userId']);
_userId = extractedUserData['userId'];
if (expiryDate.isBefore(DateTime.now())) { //for now this if statement is not running
try {
await _getAccessToken();
} catch (error) {
return false;
}
}
print('tryautologin');
notifyListeners();
_autoGetAccessToken(); //this function doesn't have any notifylisteners
return true;
}
edited:
String get accessToken {
if (_expiryDate != null &&
_expiryDate.isAfter(DateTime.now()) &&
_accessToken != null) {
return _accessToken;
}
return null;
}
bool get isAuth {
print(accessToken != null);
return accessToken != null;
}
Even the question is old quite a bit of time, this answer will help another. I have faced the problem also. this happens when you use a future builder inside a consumer. it is rebuilding again and again infinitely. So, instead of the future builder try using an if-else statement.
child: Consumer<Auth>(
builder: (ctx, auth, _) {
//Sizedbox() used for nullsafety
Widget child = SizedBox();
if (auth.isAuth) {
child = FirstScreen();
} else if (!auth.triedAutoLogin) {
auth.tryAutoLogin();
child = SplashScreen();
} else {
child = AuthScreen();
}
return MaterialApp(
title: 'MY app',
theme: ThemeData(
primarySwatch: Colors.blue,
accentColor: Colors.deepOrange,
),
home: child,
);
},
),
inside Auth class keep a boolean variable like triedAutoLogin to keep track.