How to move Future methods from Stateful widget to their own class Flutter - 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();
}
}

Related

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

I can succesfully show asset logo but i still get the error: Unable to load asset

I have a use case where i have to load the asset image based on the value of the key logo_image from a json file.
So i have this json with key logo_image and value image1.png:
{
"logo_image" : "image1.png"
}
There is an image inisde the assets folder like this: assets/image1.png
I have a class called GetAssets() with a method called getAssetItems() which loads the json file and another method called getLogo which i will use as blueprint in the view to specify which key item i want to view:
class GetAssets {
Future<Map<String, dynamic>> getAssetItems() async {
String jsonData =
await rootBundle.loadString('assets/items.json');
Map<String, dynamic> data = jsonDecode(jsonData);
return data;
Future getLogo(String key) async {
var assets = await GetAssets().getAssetItems();
return 'assets/' + assets[key];
}
}
I have a field called logo and i have method called setAssets() to load the logo and set the state of the logo with setState() and i use initState() to initialize setAssets(). I have a Scaffold() which i use to view the asset logo with the field logo:
import 'package:flutter/material.dart';
class ShowLogo extends StatefulWidget {
const ShowLogo({Key? key}) : super(key: key);
#override
_ShowLogoState createState() => _ShowLogoState();
}
class _ShowLogoState extends State<ShowLogo> {
late String logo = "";
setAssets() async {
final logoFromAsset = await GetAssets().getLogo("logo_image");
setState(() {
logo = logoFromAsset;
});
}
#override
initState() {
setAssets();
}
#override
Widget build(BuildContext context) {
return Scaffold(body: Image.asset(logo),);
}
}
I am able to show the logo on the screen of my phone, but i still get the error: Unable to load asset: What is the cause of this? How can i resolve this issue?
I think this is because of Json asset asynchronous method call.
When the widget executing then build(BuildContext context) method try to build the widget immediately after the initState (setAssets) method call.
For setAssets method's asynchronous call, build(BuildContext context) method already executed before setting logo = logoFromAsset inside setAssets method and the first time logo variable remains with empty string which is not a valid asset path, thats why Image.asset(logo) can not load the asset/image and throw an error.
But after few moment when your setAssets asynchronous method call executed and called setState this time the widget is rebuild with the non-empty/valid asset path and the logo is showing.
Try to set an default image/icon and I think this will resolve your issue.
import 'package:flutter/material.dart';
class ShowLogo extends StatefulWidget {
const ShowLogo({Key? key}) : super(key: key);
#override
_ShowLogoState createState() => _ShowLogoState();
}
class _ShowLogoState extends State<ShowLogo> {
late String logo = "";
setAssets() async {
final logoFromAsset = await GetAssets().getLogo("logo_image");
setState(() {
logo = logoFromAsset;
});
}
#override
initState() {
setAssets();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: logo != "" ? Image.asset(logo) : const Icon(Icons.image)
);
}
}

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 can I pass variable id to second screen in flutter?

I have two page and I want to use the variable 'id' in the second screen to fetch data from API.
What should I do?
Screen one: it's the product screen where user click on profile image and after that I get all information about user owner in the second screen.
Screen two: I display data for this user by id
NB: I get all the data by API
id is always Null
Screen one:
InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => UserProfile(
id: id,
)),
);
// do something here
},
),
Screen two:
class UserProfile extends StatefulWidget {
final int id;
const UserProfile({Key key, #required this.id}) : super(key: key);
#override
_UserProfileState createState() => _UserProfileState();
}
class _UserProfileState extends State<UserProfile> {
#override
void initState() {
getprofile(id);
super.initState();
}
Future<List<dynamic>> getprofile(int id) async {
var response = await Network().getData('/auth/user/$id');
data = json.decode(response.body);
return data;
}
When you want to use a property from the StatefulWidget you need to use widget.propertyName. In your case it's widget.id
class _UserProfileState extends State<UserProfile> {
#override
void initState() {
getprofile(widget.id);
super.initState();
}
Future<List<dynamic>> getprofile(int id) async {
var response = await Network().getData('/auth/user/$id');
data = json.decode(response.body);
return data;
}
Either do the same that you did before,so pass the id as a parameter to the _UserProfileState class, so just call:
_UserProfileState(#required this.id) : super();
Another option to make variables available is to use the Provider widget

How to load data from file in one stateful class and use it in another stateful class in flutter?

I am new to Flutter. I have a dart file "CreateDatabase.dart" which should fetch data from a csv file and store in a variable. In another file, "main.dart", I want to access this variable to use the fetched data.
The "CreateDatabase.dart" file looks like this:
class CreateDatabase extends StatefulWidget{
#override
State<StatefulWidget> createState() {
return CreateDatabaseState();
}
}
class CreateDatabaseState extends State<CreateDatabase> {
#override
void initState(){
super.initState();
fetchVenueDatabase();
}
List<List<dynamic>> venueDB;
List<Buildings> buildings;
List<Buildings> getBuildings(){
return this.buildings;
}
Future<String> _loadVenueDatabase() async {
return await rootBundle.loadString('assets/VenueDatabase.csv');
}
Future<List<List<dynamic>>> loadVenuedatabase() async {
String data = await _loadVenueDatabase();
List<List<dynamic>> rowsAsListOfValues = const CsvToListConverter().convert(data);
return rowsAsListOfValues;
}
fetchVenueDatabase() async{
venueDB = await loadVenuedatabase();
......
//manipulating venueDB and storing its value in this.buildings
......
this.buildings = buildings;
}
#override
Widget build(BuildContext context) {
return null;
}
}
And the main.dart file looks like this:
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _MyAppState();
}
}
class _MyAppState extends State<MyApp> {
static List<Buildings> allBuildings;
#override
void initState() { //Initial State of the app
super.initState();
CreateDatabaseState cr = CreateDatabaseState();
allBuildings = cr.getBuildings();
}
#override
Widget build(BuildContext context) {
....
}
}
But the getter method in main,
allBuildings= cr.getBuildings();
is always returning null. Where am I wrong?
You shouldnt use a Widget for that. Just create a normal class and define your methods there.
class Database {
}
List<List<dynamic>> venueDB;
List<Buildings> buildings;
List<Buildings> getBuildings(){
return this.buildings;
}
Future<String> _loadVenueDatabase() async {
return await rootBundle.loadString('assets/VenueDatabase.csv');
}
Future<List<List<dynamic>>> loadVenuedatabase() async {
String data = await _loadVenueDatabase();
List<List<dynamic>> rowsAsListOfValues = const CsvToListConverter().convert(data);
return rowsAsListOfValues;
}
fetchVenueDatabase() async{
venueDB = await loadVenuedatabase();
......
//manipulating venueDB and storing its value in this.buildings
......
this.buildings = buildings;
}
}
Then in your main class just plain and simple.
Database db = Database();
allBuildings = db.getBuildings();
Then you need to load the venues before db.getBuildings() or do some async stuff. But this should be easy to do. One option would be to make the main() method async and await for some kind of dataLoading. You could make the venueDB List static so that it holds the data independent of the Database object.
This way you can call: Database.fetchVenueData() which then must use the static List and be of course static itself.
Future main() async {
await Database.fetchVenueData();
runApp(MyApp());
}
and the modified Database class:
static List<List<dynamic>> venueDB;
static Future<void> fetchVenueDatabase() async{
venueDB = await loadVenuedatabase();
......
//manipulating venueDB and storing its value in this.buildings
......
Database.buildings = buildings;
}
But there are many more options.
Disclaimer: I have not compiled that code. So perhaps there is a missing piece or something but you should get the idea.
Disclaimer2: static variables are kind of bad. So use them wisely. They wont get garbage collected but i assume this CSV data should reside in memory anyway as long as your app is running. If this shouldnt be the case, there are far better options.