Hive for flutter just returns the Instance instead of the actual value - flutter

I have decided to go with hive as my settings/preference storage. However, I am not able to implement my Storage class correctly because the getValue method always returns Instance of 'Future<dynamic>' instead of the actual value. Does anyone know how to fix that?
My Storage class just contains the getValue and setValue which always opens the hive box and then either should set or get the value. Also, I have created the enum StorageKeys in order to have a set of keys and make sure I get or set the value to the deticated key.
main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Hive.initFlutter();
runApp(MaterialApp(
debugShowCheckedModeBanner: false,
routes: {
"/": (context) => const Home(),
},
));
}
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
get() async {
return await Storage.getValue(StorageKeys.authTokenKey);
}
void set() async {
await Storage.setValue(StorageKeys.authTokenKey, 'TestValue');
}
#override
Widget build(BuildContext context) {
set();
print(get());
return Scaffold(
backgroundColor: Colors.white,
appBar: ChevronNavigation(),
body: Container(),
);
}
}
storage.dart
class Storage {
static const preferencesBox = '_storageBox';
static Future<void> setValue(StorageKeys key, dynamic value) async {
final storage = await Hive.openBox<dynamic>(preferencesBox);
storage.put(key.toString(), value);
}
static dynamic getValue(StorageKeys key) async {
final storage = await Hive.openBox<dynamic>(preferencesBox);
return await storage.get(key.toString(), defaultValue: null) as dynamic;
}
}
enum StorageKeys {
authTokenKey,
}

print(get()); will give you Instance of Future<dynamic> since get() returns a Future object.
SOLUTION:
You need to await the actual value in the Future object by writing await before get() in a Future method.
Like this:
print(await get());
In your question above, this cannot work as the build method cannot be async. You can put the print(await get()) in a separate method and have it in your initState.
Like this:
#override
void initState() {
super.initState();
callGet();
}
Future<void> callGet() async {
print(await get());
}

You are printing the await Storage.getValue(StorageKeys.authTokenKey); value, and as it is a Future, you get this message.
You should try to call it on your initState and then get the Hive value. When the value returns you cant print it.
Eg:
#override
void initState() {
super.initState();
Storage.getValue(StorageKeys.authTokenKey).then((value) => print(value));
}

Related

make hubconnection.start() only once in the whole app

I am using the singalR package in order to use websocket in my application. During the writing of my program, I was advised to use await hubconnection.start only once and throughout the whole application. Is it possible? I thought that I could run this task somehow in the background and not make every websocket request start every time I want to use some kind of request. Is this possible or is this the wrong idea?
now I an trying to split up the whole thing and found out that in initState we can create method which will be start first before all others code in app, so I decided to split it up like that(in educational purpose I declaired connection setting globally in my file)
final httpConnectionOptions = HttpConnectionOptions(
accessTokenFactory: () => SharedPreferenceService().loginWithToken(),
skipNegotiation: true,
transport: HttpTransportType.WebSockets);
final hubConnection = HubConnectionBuilder()
.withUrl(
'http://secureLink/mysocket',
options: httpConnectionOptions,
)
.build();
this is my class:
class TestClass extends StatefulWidget {
bool? gpsenabled;
TestClass({Key? key}) : super(key: key);
#override
State<TestClass> createState() => _TestClassState();
}
class _TestClassState extends State<TestClass> {
void checkGPS() async {
if (hubConnection.state == HubConnectionState.Connected) {
await hubConnection.invoke('GPSEnable').then((value) {
setState(() {
widget.gpsenabled = value as bool;
});
print(widget.gpsenabled);
Future.delayed(Duration(seconds: 5));
checkGPS();
});
}
}
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback((_) {
hubConnection.start();
print('connection first');
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(

How to find a controller using GetX in flutter

I am trying to use Get.find to use LessonListController, but flutter tells me error,
throw '"$S" not found. You need to call "Get.put($S())" or "Get.lazyPut(()=>$S())"'
below is Lessonlistcontroller
class LessonListController extends GetxService {
final LessonListRepo lessonListRepo;
LessonListController({required this.lessonListRepo});
List<dynamic> _lessonList = [];
List<dynamic> get lessonList => _lessonList;
Future<void> getLessonList() async {
Response response = await lessonListRepo.getLessonList();
if (response.statusCode == 200) {
print('got you');
_lessonList = [];
_lessonList.addAll(Course.fromJson(response.body).lessons);
// update();
//update
} else {}
}
}
dependencies as below,
Future<void> init() async {
//api client
Get.lazyPut(() => ApiClient(appBaseUrl: AppConstants.BASE_URL));
//repos
Get.lazyPut(() => LessonListRepo(apiClient: Get.find()));
//controllers
Get.lazyPut(() => LessonListController(lessonListRepo: Get.find()));
}
here is the main.dart file
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
Get.find<LessonListController>().getLessonList();
// Get.lazyPut<LessonListController>(() =>get.() {
// };
return const GetMaterialApp(
debugShowCheckedModeBanner: false,
home: Diary(),
);
}
}
Thank you very much.
You haven't initialized the LessonListController using Get.put(LessonListController());
Get.find() is used to get the already initialized instance of Created controller.
GetxControlled works as Singleton, So it finds the already created instance every time you call Get.find() , Get.find() will only work if you have previously called Get.put or Get.lazyPut

LateInitializationError with Future

I hope you could help me!
Error saying 'tables' has not been initiliazed. But when I set tables = [] instead of
widget.data.then((result) {tables = result.tables;})
it works. I think the problem comes from my app state data which is a Future.
My simplified code:
class NavBar extends StatefulWidget {
final Future<Metadata> data;
const NavBar({Key? key, required this.data}) : super(key: key);
#override
State<NavBar> createState() => _NavBarState();
}
class _NavBarState extends State<NavBar> {
late List<MyTable> tables;
#override
void initState() {
widget.data.then((result) {
tables = result.tables;
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: buildPages(page.p)
)
);
}
Widget buildPages(index){
switch (index) {
case 0:
return ShowTablesNew(tables: tables);
case 1:
return const Details();
case 2:
return const ShowTables();
default:
return const ShowTables();
}
}
}
Future doesn't contain any data. It's an asynchronous computation that will provide data "later". The initialization error happens because the variable 'tables' is marked as late init but is accessed before the future is completed, when in fact it's not initialized yet.
Check this codelab for async programming with dart.
For your code you can use async/await in the initState method doing something like this
String user = '';
#override
void initState() {
asyncInitState();
super.initState();
}
void asyncInitState() async {
final result = await fetchUser();
setState(() {
user = result;
});
}
but since you're using a list of custom objects the most straightforward way is probably to use a FutureBuilder widget

Async Data Initialization in initState

I'm calling an async method getMyLocation() to get my current location in my initState(). The method can take a while...
I wanted to understand the behavior of initState() in these cases. Does the method still execute in the background as build() renders or does initState() timeout since it needs to complete before build() renders?
In my build() I have a statement checking if my latitude is null, in which case I return a Loading() widget. Sometimes Screen() renders and sometimes Loading() goes on indefinitely. I am assuming sometimes the getMyLocation() successfully executes during initState() and sometimes it timesout?
#override
void initState() {
super.initState();
final userData = Provider.of<MyUser>(context, listen: false);
final myUser = userData.getUser();
userData.getMyLocation();
}
getMyLocation() async {
_myUser.longitude = await getCurrentLongitude();
_myUser.latitute = await getCurrentLatitude();
notifyListeners();
}
Widget build(BuildContext context) {
final userData = Provider.of<MyUser>(context);
final myUser = userData.getUser();
myUser.latitude == null?
return Loading()
: return Screen()
Great question. First of all, initState() runs synchronously, it prepares various things needed for build() method to run properly. If you are executing some async function here, it will just return a Future because you can't await it in the initState(). In your case you probably need a FutureBuilder. The "proper way" of dealing with futures would be something like:
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
Future<void> getMyLocation() async {
final userData = Provider.of<MyUser>(context, listen: false);
final myUser = await userData.getUser();
// if getUser() is async then we have to await
myUser.longitude = await getCurrentLongitude();
myUser.latitute = await getCurrentLatitude();
// notifyListeners();
// You probably do not need this, should be done in provider methods instead
}
Widget build(BuildContext context) {
return FutureBuilder(
future: getMyLocation(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return SomeErrorWidget();
}
if (snapshot.hasData) {
return Screen(snapshot.data);
}
return SomeLoadingWidget();
});
}

Accessing Flutter context when creating StatefulWidget

I'm having trouble accessing a services object when initializing a stateful widget. The problem comes from the context object not being available in initState.
I'm using InheritedWidget to inject a services object in my main.dart file like so
void main() async {
final sqflite.Database database = await _openDatabase('db.sqlite3');
runApp(
Services(
database: database,
child: MyApp(),
),
);
}
The Services object is quite straightforward. It will have more than just the database as a member. The idea is that the widgets don't need to know if a local database, local cache, or remote server is being accessed.
class Services extends InheritedWidget {
final Database database;
const Services({
Key key,
#required Widget child,
#required this.database,
}) : assert(child != null),
assert(database != null),
super(key: key, child: child);
Future<List<models.Animal>> readAnimals() async {
return db.readAnimals(database: this.database);
}
#override
bool updateShouldNotify(InheritedWidget oldWidget) {
return false;
}
static Services of(BuildContext context) {
return context.inheritFromWidgetOfExactType(Services) as Services;
}
}
The trouble comes in my _HomePageState state when I want to load all the animals from the database. I need to access the Services object. I cannot access the Services object in initState so I am using didChangeDependencies. A problem comes when the home page is removed from the stack. It seems didChangeDependences is called and the access to the context object is illegal. So I created an _initialized flag that I can use in didChangeDependencies to ensure I only load the animals the first time. This seems very inelegant. Is there a better way?
class _HomePageState extends State<HomePage> {
bool _initialized = false;
bool _loading = false;
List<Animal> _animals;
#override
Widget build(final BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(Strings.of(this.context).appName),
),
body: _HomeBody(
loading: this._loading,
animals: this._animals,
),
);
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
if (!this._initialized) {
this._initialized = true;
this._loadAnimals();
}
}
void _loadAnimals() async {
this.setState(() {
this._loading = true;
this._animals = null;
});
final List<Animal> animals = await Services.of(this.context).readAnimals();
this.setState(() {
this._loading = false;
this._animals = animals;
});
}
}
For that case you could use addPostFrameCallback of your WidgetsBinding instance to execute some code after your widget was built.
_onLayoutDone(_) {
this._loadAnimals();
}
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback(_onLayoutDone);
super.initState();
}