How to make methods in Future class Flutter - flutter

how do I make a method inside Future class? I wanted to call the methods in other dart files, but because I made the functions as Future, it does not get called by the instance name.
This is the example code that I want to call in another dart file.
Future tokenDb() async{
final database = openDatabase(
join(await getDatabasesPath(), 'token_list.db'),
onCreate: (db, version) {
return db.execute(
"CREATE TABLE tokens (token INTEGER PRIMARY KEY)",
);
},
version: 1,
);
Future<void> insertToken(Token token) async {
final Database db = await database;
await db.insert(
'tokens',
token.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
}
and I need it here:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
MaterialApp(home: MyApp()),
);
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late FirebaseMessaging messaging;
String tokenValue = "";
#override
void initState() {
messaging = FirebaseMessaging.instance;
messaging.getToken().then((value) {
tokenValue = value!;
Clipboard.setData(ClipboardData(text: tokenValue));
print(tokenValue);
var user1 = Token(token: tokenValue);
print("user1 token : " + tokenValue);
**var db = tokenDb();
db.insertToken(user1);
tokenDb();**
// Maybe I need to call the function here?
});
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: AuthTypeSelector(),
),
);
}
}
I don't have a lot of knowledge of Flutter... Thanks for your help!! I really appreciate it. :)

Related

How to move Future methods from Stateful widget to their own class Flutter

I have several Future methods contained within a Stateful widget. Identical methods appear in different parts of the app. I want to add the methods to one Class instead of rewriting four times in the app but I haven't been able to figure it out because several of the methods call setState to update the UI. The methods are called when users choose an image from their gallery, choose to take a photo, upload the selected image to the database for display in the app, the image is compressed, etc.
class ExampleClass extends StatefulWidget {
const ExampleClass({Key? key}) : super(key: key);
#override
State<ExampleClass> createState() => _ExampleClassState();
}
class _ExampleClassState extends State<ExampleClass> {
File? file;
Future<void> _captureImageWithCamera() async {
Get.back();
XFile? pickedFile = await ImagePicker().pickImage(
source: ImageSource.camera,
);
setState(() {
file = File(pickedFile!.path);
});
}
#override
Widget build(BuildContext context) {
return Container();
}
}
I have only included one of the methods (captureImageWithCamera) assuming that a suggested solution for one could be applied to all of them. If I need to provide more code I will be happy to do so. Any help will be greatly appreciated.
when you are using third party library to something outside your app like picking a photo, downloading some data, making http calls.
consider to create service class for this stuff, service class is basically this:
a class with single responsibility, like this class should do only one thing.
in your case you should create ImagePickerService the class job is pick an Image and return it to you nothing more, you should not call any flutter framework inside it
like setState because it's not his job to update the UI.
class ImagePickerService {
//make this class singleton so you do not make a new instance every time you want it.
static final ImagePickerService _instance = ImagePickerService._();
ImagePickerService._();
factory ImagePickerService() => _instance;
Future<XFile?> pickImage(ImageSource imageSource) async {
return await ImagePicker().pickImage(
source: imageSource,
);
}
}
so now when ever you want to pick an Image you just need to call this service like this.
onTap: () async {
final file = await ImagePickerService().pickImage(ImageSource.camera);
setState(() {
// you got the file boss do anything you want
});
}
now when you create a new page you just create page and defined some services inside it.
You can return pickedFile from your extracted method, then each class which uses the result can call setState itself, using the returned value.
class ImageCapturer {
Future<XFile?> captureImageWithCamera() async {
Get.back();
XFile? pickedFile = await ImagePicker().pickImage(
source: ImageSource.camera,
);
return pickedFile;
}
}
class ExampleClass extends StatefulWidget {
const ExampleClass({Key? key}) : super(key: key);
#override
State<ExampleClass> createState() => _ExampleClassState();
}
class _ExampleClassState extends State<ExampleClass> {
File? file;
Future<void> _captureImageWithCamera() async {
final file = await ImageCapturer().captureImageWithCamera();
setState(() {
this.file = File(file!.path);;
});
}
#override
Widget build(BuildContext context) {
return Container();
}
}
You could abstract the code away to one function with a callback parameter, like so:
class OtherExampleClass {
Future<void> captureImageWithCamera(
void Function(XFile? pickedFile) callback,
) async {
Get.back();
XFile? pickedFile = await ImagePicker().pickImage(
source: ImageSource.camera,
);
callback(pickedFile);
}
}
class ExampleClass extends StatefulWidget {
const ExampleClass({Key? key}) : super(key: key);
#override
State<ExampleClass> createState() => _ExampleClassState();
}
class _ExampleClassState extends State<ExampleClass> {
File? file;
Future<void> _captureImageWithCamera() async {
await OtherExampleClass().captureImageWithCamera((XFile? pickedFile) {
setState(() {
file = File(pickedFile!.path);
});
}
);
}
#override
Widget build(BuildContext context) {
return Container();
}
}

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

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

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));
}

How to store and pass SharedPreference value to other pages in flutter?

When user logs into the app I need to set 'PWD' in the shared_preference variable. I need to get that value in splashcreen of my app so that when user opens the app again it need redirect to only password entering page. How can I do it in flutter.
onPressed: () async {
SharedPreferences prefs = await SharedPreferences.getInstance();
appdata.loginmode = prefs.setString('LOGIN_MODE', 'PWD');
Navigator.push(
context,
MaterialPageRoute(builder: (context) => BottomNavigation()),
);
print('Shared....');
print(prefs.getString('LOGIN_MODE'));
},
This what I am doing when user click login it will set to 'PWD', then I need to call the prefs in splashscree.
Short Answer
Not for splash screen but I am using the same logic for the onboard screen. I hope this answer will help. So, on your main.dart file, create a nullable int onBoardCount, outside of any class, you're gonna need this on your splash screen. Also, instantiate SharedPreferences in main and pass it with onboardcount to you MyApp();
int? onBoardCount;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences prefs = await SharedPreferences.getInstance();
// Get onboard count from prefs, if it already exists,if not it will return null
onBoardCount = prefs.getInt('onBoardKey');
runApp(MyApp(prefs,onBoardCount));
}
Now, your MyApp file should be something like
class MyApp extends StatefulWidget {
late SharedPreferences prefs;
....
MyApp(this.prefs,this.onBoardCount, ...);
Now in your splash_screen.dart use the following logic.
void onSubmitDone(AppStateProvider stateProvider, BuildContext context) {
await prefs.setInt('onBoardKey', 0);
// Some route logic like route.push("/home");
}
Long Answer
I am using Go Router for routing and Provider for state management so, here's my app's code.
Main.dart
int? onBoardCount;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences prefs = await SharedPreferences.getInstance();
onBoardCount = prefs.getInt('onBoardKey');
....
runApp(MyApp(prefs, onBoardCount));
}
I have a separate MyApp file to reduce congestion.
my_app.dart
class MyApp extends StatefulWidget {
late SharedPreferences prefs;
int? onBoardCount;
MyApp(this.prefs, this.onBoardCount,..... {Key? key})
: super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// The appstate provider is handling app level state
late AppStateProvider appStateProvider;
#override
void didChangeDependencies() {
super.didChangeDependencies();
appStateProvider = AppStateProvider(
widget.onBoardCount, widget.prefs,....);
}
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
....
ChangeNotifierProvider(
create: (context) => AppStateProvider(
widget.onBoardCount,
widget.prefs,...)),
Provider(
create: (context) => AppRouter(
appStateProvider: appStateProvider,
onBoardCount: widget.onBoardCount,
prefs: widget.prefs,
),
),
],
child: Builder(
builder: ((context) {
final GoRouter router = Provider.of<AppRouter>(context).router;
return MaterialApp.router(
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate);
}),
),
);
}
}
App State Provider File
Create a function to update onboard logic and notify listeners.
class AppStateProvider with ChangeNotifier {
AppStateProvider(this.onBoardCount, this.prefs,..);
int? onBoardCount;
late SharedPreferences prefs;
bool? _isOnboarded;
bool get isOnboard => _isOnboarded as bool;
void hasOnBoarded() async {
await prefs.setInt('onBoardKey', 0);
_isOnboarded = true;
notifyListeners();
}
}
On Router file
class AppRouter {
late AppStateProvider appStateProvider;
late SharedPreferences prefs;
int? onBoardCount;
AppRouter({
required this.appStateProvider,
required this.onBoardCount,
required this.prefs,
});
get router => _router;
late final _router = GoRouter(
refreshListenable: appStateProvider,
initialLocation: "/",
routes: [
...
],
redirect: (state) {
final String onboardLocation =
state.namedLocation("Your Route name");
bool isOnboarding = state.subloc == onboardLocation;
bool? toOnboard = prefs.containsKey('onBoardKey') ? false : true;
print("Is LoggedIn is $isLoggedIn");
if (toOnboard) {
return isOnboarding ? null : onboardLocation;
}
return null;
});
}
Since the router is listening to appStateProvider, it will change once you call hasOnBoarded() on your onboard screen.
OnBoardScreen
void onSubmitDone(AppStateProvider stateProvider, BuildContext context) {
stateProvider.hasOnBoarded();
GoRouter.of(context).go("/");
}
I hope this will help please leave comments. FYI, ... is some other codes that I feel it's not important for this topic.

shared_preferences returns null on existing value

I am trying to see if an id key is available in my app's shared_pereferences and if there is, redirect my user to the homepage. I am checking the Id in the initState() function of my main.dart and I know that the id exists because I can get it in other pages. but in my main.dart it returns null. any ideas?
here is my main.dart code:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import './ui/pages/auth/auth_one.dart';
import './ui/pages/main_page.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitDown, DeviceOrientation.portraitUp])
.then((_) => runApp(MyApp()));
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
String userId;
#override
void initState() {
_getUserId().then((id) => userId = id);
super.initState();
}
#override
Widget build(BuildContext context) {
print(userId);
return MaterialApp(
theme: ThemeData(primarySwatch: Colors.deepPurple),
debugShowCheckedModeBanner: false,
home: userId == null ? AuthOne() : MainPage(),
);
}
_getUserId() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var id = prefs.getString('id');
return id;
}
}
Your _getUserId method is async, so you will have to refresh the widget after you get the result.
Use this:
#override
void initState() {
_getUserId().then((id) {
//calling setState will refresh your build method.
setState(() {
userId = id;
});
});
super.initState();
}
This is happening because you are trying to use the value before its calculated.
you could use timer function for delay