Injecting floor database dependency through GETX is not working : Flutter - flutter

I am new at using Getx for state management. I am trying to inject the dependency of my DB instance in main by Getx through initial binding I am using the floor database. can anyone help me with this. where I went wrong?
this is how my register function looks like
void registerdbInstance() {
Get.lazyPut(<AppDatabase>() async =>
{await $FloorAppDatabase.databaseBuilder('app_database.db').build()});
}
this is how my main app widget looks like
#override
Widget build(BuildContext context) {
return GetMaterialApp(
title: appName,
initialBinding: BindingsBuilder.put(() => registerdbInstance),
theme: ThemeData(
fontFamily: 'Montserrat',
backgroundColor: sdWhiteColor,
colorScheme: ColorScheme.fromSwatch()
.copyWith(primary: sdPrimaryColor, secondary: sdSecondaryColor),
),
getPages: routeList,
home: ServiceDeskHome(),
);
initialBinding: BindingsBuilder.put(() => registerdbInstance),
this is how I am trying to access this dependency
var db = Get.find();
The problem is that Getx is not able to find the dependency.
"AppDatabase" not found. You need to call "Get.put(AppDatabase())" or "Get.lazyPut(()=>AppDatabase())"

If i recall corectly when you are working with databases in getx you have to do it asynchronously and use Get.putAsync if you want to register an asynchronous instance (LINK). I would consider using fenix parameter as well.

I initialized it in this way.
it will initialize when the app starts, you can use it later anywhere by simply calling Get.find()
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Get.putAsync<AppDatabase>(permanent: true, () async {
final db = await $FloorAppDatabase
.databaseBuilder('todotask.db')
.build();
return db;
});
await GetStorage.init();
runApp(const MyApp());
}

Related

Flutter Getx , Wait till all main binding load before Navigation

I am using getx on my project, I have a mainBianding page :
class MainBinding implements Bindings {
#override
Future<void> dependencies() async {
Get.putAsync<HiveService>(() => HiveService().init(), permanent: true);
Get.lazyPut<HomeController>(
() => HomeController(
dbclient: Get.find<HiveService>()),
);
}
}
I have a GETXService for initializing Hive
class HiveService extends GetxService {
late Box<Model> vBox;
Future<HiveService> init() async {
final appDocumentDirectory =
await path_provider.getApplicationDocumentsDirectory();
Hive
..init(appDocumentDirectory.path)
..registerAdapter<Model>(ModelAdaptor())
return this;
}
After lunching App HomePage and HomeController will be launched but I got this error:
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following message was thrown building Builder:
"HiveService" not found. You need to call "Get.put(HiveService())" or
"Get.lazyPut(()=>HiveService())"
The relevant error-causing widget was:
ScrollConfiguration
because Hive service is a future Is has a delay to be loaded but I used Future<void> dependencies() async on this binding class. How do I have to wait to be sure HiveService load completely and after that Home Page load?
I am using MainBinding Inside GetMaterialApp;
Future main() async {
await MainBinding().dependencies();
...
runApp(MyApp()
return GetMaterialApp(
debugShowCheckedModeBanner: false,
useInheritedMediaQuery: true,
locale: DevicePreview.locale(context),
theme: ThemeData(
primarySwatch: Colors.blue,
),
initialRoute: Routes.HOME,
getPages: AppPages.pages,
initialBinding: MainBinding(),
test this
await Get.putAsync<HiveService>(() => HiveService().init(), permanent: true);
You need to run your service.init() function first. and then return the getx Service(HiveService) and complete to load this service to the memory.
#override
Future<void> dependencies() async {
await Get.putAsync< HiveService >(() async {
final HiveService service = HiveService();
await service.init();
return service;
});
Get.lazyPut<HomeController>(
() => HomeController(
dbclient: Get.find<HiveService>()),
);
}
}

App loads a wrong screen before loading the correct screen

In my flutter app, I have two screens namely, AppUpdate and StoreTiming. The AppUpdate screen is shown if the current build is not equal to enforced build (See code below). If current build is equal to enforced build, then the StoreTiming screen is shown. In the case of current build = enforced build, when I start the app, I see the AppUpdate screen appear for about a second and then it is replaced by StoreTming screen. Why does this happen?
void initState(){
super.initState();
_initPackageInfo();
_enforcedVersion();
}
Future<void> _initPackageInfo() async {
final info = await PackageInfo.fromPlatform();
setState(() {
_packageInfo = info;
});
}
Future<void> _enforcedVersion() async {
final RemoteConfig remoteConfig = RemoteConfig.instance;
await remoteConfig.setConfigSettings(RemoteConfigSettings(
fetchTimeout: const Duration(seconds: 10),
minimumFetchInterval: Duration.zero,
));
await remoteConfig.fetchAndActivate();
setState(() {
enforcedBuildNumber = remoteConfig.getString('enforced_build_number');
});
}
Widget build(BuildContext context) {
return !(_isLoading)?MultiProvider(
providers: [],
child: Consumer<Auth>(builder: (ctx, auth, _) =>
MaterialApp(
navigatorKey: navigatorKey,
title: 'MyAppName',
theme: ThemeData(
textTheme: Theme.of(context).textTheme.apply(
bodyColor: Colors.black,
displayColor: Colors.black,
),
primaryColor: Colors.white,
accentColor: Color(0xFFF2AD18),
fontFamily: 'Muli',
),
home:
_packageInfo.buildNumber!=enforcedBuildNumber?AppUpdateScreen():StoreTimings(),
routes:{},
),)):Center(child:Loading());}}
Because you make _initPackageInfo(); and _enforcedVersion(); async and async functions need time to complete their works! So before set a correct value on fields in this function build method called and show a page. you can await this function before load completely and set correct values then show a page. but while a async functions is doing their own job you must show some thing to user! You can show loading page if you want and if values are ready show a page...
I recommend you to use a state management library like flutter_bloc to have a clean code and better functionallity.

How do I set up navigator using Getx and Auto Route?

Problem:
I am having trouble setting up navigation using GetX and AutoRoute.
Code Setup:
According to the GetX documentation, if you want to use GetX navigation you have to replace MaterialApp() with GetMaterialApp(). You also set the routes.
void main() {
runApp(
GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.zoom
),
],
)
);
}
The AutoRoute example uses MaterialApp.router() to set up the routerDelegate and routeInformationParser.
final _appRouter = AppRouter()
...
Widget build(BuildContext context){
return MaterialApp.router(
routerDelegate: _appRouter.delegate(...initialConfig),
routeInformationParser: _appRouter.defaultRouteParser(),
),
}
Here is how I set up the navigation according to Getx and AutoRoute:
void main() {
configureDependencies();
runApp(Portfolio());
}
class Portfolio extends StatelessWidget {
final _appRouter = AppRouter.Router();
#override
Widget build(BuildContext context) {
return GetMaterialApp.router(
routerDelegate: _appRouter.delegate(),
routeInformationParser: _appRouter.defaultRouteParser(),
builder: (context, extendedNav) => Theme(
data: ComplexReduxTheme.complexReduxLightTheme,
child: extendedNav ?? Container(color: Colors.red),
),
);
}
}
I am using GetMaterialApp.router which returns a GetMaterialApp. Despite this, I get the error "You are trying to use contextless navigation without a GetMaterialApp or Get.key.". I have tried setting up the navigator key and setting Get.testMode = true but nothing happens(no error) when I try to navigate to another screen.
Desired Result:
I should be able to navigate to the desired screen via Get.toNamed().
Current Result:
I get the following error from GetX when trying to navigate to another screen using Get.toNamed() : "You are trying to use contextless navigation without
a GetMaterialApp or Get.key.
If you are testing your app, you can use:
[Get.testMode = true], or if you are running your app on
a physical device or emulator, you must exchange your [MaterialApp]
for a [GetMaterialApp]."
AutoRoute Version: 2.2.0
Get Version: 4.1.4
You don't need external routing plugin, GetX already did that for you, and if you want to navigate, just use Get.toNamed("/some-page") and it will show you the page you wanted. Same goes to nested route.
For Example
GetPage(
name: '/third',
page: () => Third(),
transition: Transition.zoom,
children: [
GetPage(
name: '/child-of-third',
page: () => ChildOfThird(),
),
],
),
// You access it like this
Get.toNamed("/third");
// And this one, for the nested page
Get.toNamed("/third/child-of-third");
The reason you got the error is when you use external routing plugin in GetX, it will generate their own code, with their own context in their own ecosystem. GetX doesn't know which context does the plugin use since it was outside of its lifecycle.
I was facing the same issue when combining both getx and auto router in my case i needed nested navigation as well I created a work around like this
I created initial bindings and passed appRouter to it and saved it in getx routing controller that i was using to a method like Get.toNamed because with initial appRouter you don't need context you can navigate like this
// main app widget
class _myAppState extends State<MyApp> {
final _appRouter = AppRouter();
#override
Widget build(BuildContext context) {
return GetMaterialApp.router(
routerDelegate: _appRouter.delegate(),
routeInformationParser: _appRouter.defaultRouteParser(),
initialBinding: InitialBinding(router: _appRouter,),
);
}
}
// initial binding to store to store app router
class InitialBinding extends Bindings {
AppRouter router;
InitialBinding({required this.router,});
#override
void dependencies() {
Get.put(NavRoutesController(router: router,),permanent: true);
}
}
// router controller
class NavRoutesController extends GetxController {
AppRouter router;
NavRoutesController({required this.router,});
void toNamed(String route){
router.pushNamed(route);
}
}
//to navigate use
final router = Get.find<RouterController>();
router.toNamed("/some")
//or
Get.find<RouterController>().toNamed("/some")
// you can get base context as well from AppRouter like this
Get.find<RouterController>().router.navigatorKey.currentState.context

Where do I run initialisation code when starting a flutter app?

Where do I run initialisation code when starting a flutter app?
void main() {
return runApp(MaterialApp(
title: "My Flutter App",
theme: new ThemeData(
primaryColor: globals.AFI_COLOUR_PINK,
backgroundColor: Colors.white),
home: RouteSplash(),
));
}
If I want to run some initialisation code to, say fetch shared preferences, or (in my case) initialise a package (and I need to pass in the the BuildContext of the MaterialApp widget), what is the correct way to do this?
Should I wrap the MaterialApp in a FutureBuilder? Or is there a more 'correct' way?
------- EDIT ---------------------------------------------------
I have now placed the initialisation code in RouteSplash() widget. But since I required the BuildContext of the app root for the initialisation, I called the initialisation in the Widget build override and passed in context.ancestorInheritedElementForWidgetOfExactType(MaterialApp). As I don't need to wait for initialisation to complete before showing the splash screen, I haven't used a Future
One simple way of doing this will be calling the RouteSplash as your splash screen and inside it perform the initialization code as shown.
class RouteSplash extends StatefulWidget {
#override
_RouteSplashState createState() => _RouteSplashState();
}
class _RouteSplashState extends State<RouteSplash> {
bool shouldProceed = false;
_fetchPrefs() async {
await Future.delayed(Duration(seconds: 1));// dummy code showing the wait period while getting the preferences
setState(() {
shouldProceed = true;//got the prefs; set to some value if needed
});
}
#override
void initState() {
super.initState();
_fetchPrefs();//running initialisation code; getting prefs etc.
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: shouldProceed
? RaisedButton(
onPressed: () {
//move to next screen and pass the prefs if you want
},
child: Text("Continue"),
)
: CircularProgressIndicator(),//show splash screen here instead of progress indicator
),
);
}
}
and inside the main()
void main() {
runApp(MaterialApp(
home: RouteSplash(),
));
}
Note: It is just one way of doing it. You could use a FutureBuilder if you want.
To run code at startup, put it in your main.dart. Personally, that's the way I do it, to initialise lists etc.

Opening keyboard causes stateful widgets to be re-initialized

I am using Flutter 1.2.1 in the Stable branch. To illustrate my problem imagine I have pages A and B. A navigates to B using Navigator.push and B navigates back to A using Navigator.pop. Both are stateful widgets.
When I navigate from A to B and then pop back to A everything is fine and A keeps its state. However, if I navigate from A to B, tap a textfield in B opening the keyboard, then close the keyboard and pop back to A, A's entire state is refreshed and the initState() method for A is called again. I verified this by using print statements.
This only happens when I open the keyboard before popping back to A. If I navigate to B, then immediately navigate back to A without interacting with anything then A keeps its state and is not re-initialized.
From my understanding the build method is called all the time but initState() should not get called like this. Does anyone know what is going on?
After much trial and error I determined the problem. I forgot that I had setup a FutureBuilder for the / route in my MaterialApp widget. I was passing a function call that returns a future to the future parameter of the FutureBuilder constructor rather than a variable pointing to a future.
So every time the routes got updated a brand new future was being created. Doing the function call outside of the MaterialApp constructor and storing the resulting future in a variable, then passing that to the FutureBuilder did the trick.
It doesn't seem like this would be connected to the weird behavior I was getting when a keyboard opened, but it was definitely the cause. See below for what I mean.
Code with a bug:
return MaterialApp(
title: appTitle,
theme: ThemeData(
primarySwatch: Colors.teal,
accentColor: Colors.tealAccent,
buttonColor: Colors.lightBlue,
),
routes: {
'/': (context) => FutureBuilder<void>(
future: futureFun(), //Bug! I'm passing a function that returns a future when called. So a new future is returned each time
builder: (context, snapshot) {
...
}
...
}
...
}
Fixed Code:
final futureVar = futureFun(); //calling the function here instead and storing its future in a variable
return MaterialApp(
title: appTitle,
theme: ThemeData(
primarySwatch: Colors.teal,
accentColor: Colors.tealAccent,
buttonColor: Colors.lightBlue,
),
routes: {
'/': (context) => FutureBuilder<void>(
future: futureVar, //Fixed! Passing the reference to the future rather than the function call
builder: (context, snapshot) {
...
}
...
}
...
}
did you use AutomaticKeepAliveClientMixin in "A" widget ?
if you don't , see this https://stackoverflow.com/a/51738269/3542938
if you already use it , please give us a code that we can test it directly into "main.dart" to help you
Yup, happened to me, perhaps it's much better to wrap the FutureBuilder itu a PageWidget, and make it singleton
return MaterialApp(
title: appTitle,
theme: ThemeData(
primarySwatch: Colors.teal,
accentColor: Colors.tealAccent,
buttonColor: Colors.lightBlue,
),
routes: {
'/': (context) => PageWidget() // wrap it by PageWidget
...
}
...
}
class PageWidget extends StatelessWidget {
static final _instance = PageWidget._internal(); // hold instance
PageWidget._internal(); // internal consturctor
factory PageWidget() {
return _instance; // make it singleton
}
#override
Widget build(BuildContext context) {
return FutureBuilder<void>( ... );
}
}
I got a solution, I was initialising variables in the constructor of the superclass. I removed it and worked!
I just removed the FutureBuilder from the home of MaterialApp and changed the MyApp into a Stateful widget and fetched the requisite info in the initState and called setState in the .then(); of the future and instead of passing multiple conditions in the home of MaterialApp, I moved those conditions to a separate Stateful widget and the issue got resolved.
initState:
#override
void initState() {
// TODO: implement initState
// isSignedIn = SharedPrefHelper.getIsSignedIn();
getIsSignedInFromSharedPreference().then((value) {
setState(() {
isSignedInFromSharedPref = value ?? false;
if (isSignedInFromSharedPref) {
merchantKey = LocalDatabase.getMerchantKeyWithoutAsync();
}
isLoadingSharedPrefValue = false;
});
});
super.initState();
}
Future<bool?> getIsSignedInFromSharedPreference() async {
return SharedPrefHelper.getIsSignedIn();
}
MaterialApp (now):
MaterialApp(
title: 'Loveeatry POS',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Home(
isLoadingSharedPrefValue: isLoadingSharedPrefValue,
isSignedInFromSharedPref: isSignedInFromSharedPref,
merchantKey: merchantKey,
),
),
Home:
class Home extends StatelessWidget {
final bool isLoadingSharedPrefValue;
final bool isSignedInFromSharedPref;
final String merchantKey;
const Home({
Key? key,
required this.isLoadingSharedPrefValue,
required this.isSignedInFromSharedPref,
required this.merchantKey,
}) : super(key: key);
#override
Widget build(BuildContext context) {
if (!isLoadingSharedPrefValue) {
if (isSignedInFromSharedPref) {
return const Homepage(
shouldLoadEverything: true,
);
} else if (merchantKey.isNotEmpty) {
return LoginPage(merchantKey: merchantKey);
} else {
return const AddMerchantKeyPage();
}
} else {
return loading(context);
}
}
}
P.S.: If you need any more info, please leave a comment.