I'm new to flutter.
I'm using SharedPreferences to store data.
I have this dynamic function :
_loadFromLocal(index) async {
var data = await getFromLocal();
return data?.elementAt(index);
}
Future<List<String>?> getFromLocal() async {
SharedPreferences pref = await SharedPreferences.getInstance();
return pref.getStringList('data_prices');
}
I need to call this dynamic fucntion _loadFromLocal in Widget build
class _ScanState extends State<Scan> {
#override
Widget build(BuildContext context) {
return
Scaffold(
child: Container(
child:Text(
_loadFromLocal(0),
)),);
}
}
How can I do that
You can't call async code as you mentioned, instead you should call it in initState() like below
class _ScanState extends State<Scan> {
dynamic value;
#override
void initState() {
super.initState();
_loadFromLocal(0).then((data) {
setState(() {
value = data;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Text(
value ?? '',
),
),
);
}
// omit the rest code
}
Related
I want to call the string function, I use package_info_plus to get the packageName when I run the app, just like this:
class SplashScreenController extends GetxController {
late String one = '';
late RxString two = ''.obs;
#override
void onInit() {
_initPackageInfo();
_setImage();
Timer(Duration(seconds: 5), () => Get.offNamed(Routes.DASHBOARD));
super.onInit();
}
#override
void onClose() {}
Future<void> _initPackageInfo() async {
final info = await PackageInfo.fromPlatform();
one = info.packageName;
}
String _setImage() {
if (one == 'com.package.one') {
return two.value = Images.one;
} else if (one == 'com.package.two) {
return two.value = Images.two;
} else {
return two.value = Images.one;
}
}
}
And when I try to call RxString two inside Image.asset()
#override
Widget build(BuildContext context) {
print(controller.two);
return Obx(() => Scaffold(
body: Container(
color: const Color.fromARGB(255, 255, 255, 255),
alignment: AlignmentDirectional.center,
child: controller.one.isNotEmpty
? Image.asset(controller.two.toString())
: const SizedBox.shrink(),
)));
}
It show the null value, is there any way for me to use the packageName value in conditional case and then calling it in Image.asset() widget?
This is not related really to Getx, it's related to the Dart language programming and futures, here in this piece of code:
#override
void onInit() {
_initPackageInfo();
_setImage();
Timer(Duration(seconds: 5), () => Get.offNamed(Routes.DASHBOARD));
super.onInit();
}
_initPackageInfo() is an asynchronous method, which needs time to resolve (so the info variable gets an PackageInfo instance ), this means that executing it synchronously will still get you the instance, but it will not wait for it, and it will run _setImage() immediately which at this point PackageInfo.fromPlatform() did not get resolved yet, so it's null, what you need to do is to make it wait until in have the instance then continues running other code :
#override
void onInit() {
_initPackageInfo().then((val) {
// this will be executed when _initPackageInfo() finishes.
_setImage();
Timer(Duration(seconds: 5), () => Get.offNamed(Routes.DASHBOARD));
});
super.onInit();
}
I have rewrite your controller and UI Code. Please check it out.
Controller (SplashScreen)
class SplashScreenController extends GetxController {
late RxString one = ''.obs;
late RxString two = ''.obs;
#override
void onInit() async {
await _initPackageInfo();
Timer(Duration(seconds: 5), () => Get.offNamed(Routes.DASHBOARD));
super.onInit();
}
#override
void onClose() {}
Future<void> _initPackageSetImage() async {
final info = await PackageInfo.fromPlatform();
one.value = info.packageName;
switch(one.value){
case 'com.package.one':
two.value = Images.one;
break;
case 'com.package.two':
two.value = Images.two;
break;
default:
two.value = Images.one;
break;
}
}
}
UI (SplashScreen)
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: const Color.fromARGB(255, 255, 255, 255),
alignment: AlignmentDirectional.center,
child: Obx((){
print(controller.two.value);
if(controller.one.value.isNotEmpty){
return Image.asset(controller.two.value);
}
return const SizedBox.shrink();
}),
));
}
I am trying to implement Autocomplete Widget and the items are present in a text file. While reading the file facing "A value of type 'Future<List?>' can't be assigned to a variable of type 'List?'." What am i missing?
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class ContractControl extends StatefulWidget {
const ContractControl({super.key});
#override
State<ContractControl> createState() => _ContractControlState();
}
class _ContractControlState extends State<ContractControl> {
//static const List<String> listItems = <String>['Apple', 'Banana'];
Future<List<String>?> getData() async {
try {
String fileData = await rootBundle.loadString('assets/instruments.txt');
List<String> lines = fileData.split('\n');
return lines;
} catch (e) {
throw (e.toString());
}
}
#override
Widget build(BuildContext context) {
List<String>? listItems = getData(); **--> here**
return Scaffold(
appBar: AppBar(title: const Text('Contract Control')),
body: Autocomplete<String>(
optionsBuilder: (TextEditingValue textEditingValue) {
Future<List<String>?> listItems = getData();
if (textEditingValue.text == '') {
return const Iterable<String>.empty();
}
return listItems.where((String item) {
return item.contains(textEditingValue.text.toUpperCase());
});
},
onSelected: (String item) {
print('Item selected');
},
),
);
}
}
getData() is async method. It is Future method, meaning it takes some time to execute. It should be like that: List<String>? listItems = await getData(); But it shows error because you are in build method which is not async. The best solution here is FutureBuilder widget!
I would like to break down my Scaffold into smaller pieces for easy read. I separate widgets into functions and return to the scaffold tree. But I don't know how to make use of the function declared inside the stateful widget which need to setState the UI.
Part of my code:
Future<List<dataRecord>>? dataList;
class _clientDetailState extends State<clientDetail> {
#override
void initState() {
super.initState();
}
List<dataRecord> parseJson(String responseBody) {
final parsed =
convert.jsonDecode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<dataRecord>((json) => dataRecord.fromJson(json)).toList();
}
Future<List<dataRecord>> fetchData(http.Client client) async {
final response = await client
.get(Uri.parse('test.php'));
return parseJson(response.body);
}
Body: myButton,
ListView,
Widget myButton() {
return TextButton(
child: Text('test'),
onTap: () {
dataList = fetchData(http.Client()); //Method not found
},
}
Here is simple way to do
class ClientDetail extends StatefulWidget {
const ClientDetail({Key? key}) : super(key: key);
#override
State<ClientDetail> createState() => _ClientDetailState();
}
class _ClientDetailState extends State<ClientDetail> {
List<dataRecord> dataList = [];
#override
Widget build(BuildContext context) {
return ListView(
children: [
myButton(),
...dataList.map((e) => Text(e)).toList(),
],
);
}
List<dataRecord> parseJson(String responseBody) {
final parsed =
convert.jsonDecode(responseBody).cast<Map<String, dynamic>>();
return parsed.map<dataRecord>((json) => dataRecord.fromJson(json)).toList();
}
Future<List<dataRecord>> fetchData(http.Client client) async {
final response = await client.get(Uri.parse('test.php'));
return parseJson(response.body);
}
Widget myButton() {
return TextButton(
child: const Text('test'),
onPressed: () async {
setState(() async {
dataList = await fetchData(http.Client());
});
});
}
}
Tip: always start class name with capital letter, e.g. ClientDetail instead of clienDetail also DataRecord instead of dataRecord
Regards
You can pass your actual function as a parameter to the widget's function and then call it directly from state;
Body: myButton(onPressed: () => fetchData(http.Client())),
ListView,
Widget myButton({required void Function()? onPressed}) {
return TextButton(
child: Text('test'),
onPressed: onPressed,
);
}
GetX works fine and update data if element have controller. But how to get it work if we have not direct to controller and widget done in way of changing date with onChange.
I created small copy-paste example:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:web_date_picker/web_date_picker.dart';
void main() {
Get.put(SearchFormController());
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'GetX Demo',
home: MyHomePage(),
);
}
}
class SearchFormController extends GetxController {
var endDate = Rxn<DateTime?>();
setToNow() { // This function should set widget value
endDate.value = DateTime.now();
print('setToNow event: ${endDate.value}');
}
}
class MyHomePage extends StatelessWidget {
var ctrl = Get.find<SearchFormController>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Row(
children: [
Obx(() => SizedBox(
width: 160,
child: WebDatePicker(
initialDate: ctrl.endDate.value,
onChange: (value) {
if (value != null) {
ctrl.endDate.value = value;
print('onChange event: ${ctrl.endDate.value}');
}
},
),
)),
ElevatedButton(
onPressed: () {
ctrl.setToNow();
},
child: Text("Set Date"),
),
],
),
);
}
}
pubspec.yaml:
dependencies:
flutter:
sdk: flutter
web_date_picker: ^1.0.0+3
get: ^4.6.5
In this code I can't set date by clicking on Set Date button.
I looked at widget code and it's controller is hidden with follow realization:
class _WebDatePickerState extends State<WebDatePicker> {
final FocusNode _focusNode = FocusNode();
late OverlayEntry _overlayEntry;
final LayerLink _layerLink = LayerLink();
final _controller = TextEditingController();
late DateTime? _selectedDate;
late DateTime _firstDate;
late DateTime _lastDate;
bool _isEnterDateField = false;
#override
void initState() {
super.initState();
_selectedDate = widget.initialDate;
_firstDate = widget.firstDate ?? DateTime(2000);
_lastDate = widget.lastDate ?? DateTime(2100);
if (_selectedDate != null) {
_controller.text = _selectedDate?.parseToString(widget.dateformat) ?? '';
}
_focusNode.addListener(() {
if (_focusNode.hasFocus) {
_overlayEntry = _createOverlayEntry();
Overlay.of(context)?.insert(_overlayEntry);
} else {
_controller.text = _selectedDate.parseToString(widget.dateformat);
widget.onChange.call(_selectedDate);
_overlayEntry.remove();
}
});
}
void onChange(DateTime? selectedDate) {
_selectedDate = selectedDate;
_controller.text = _selectedDate.parseToString(widget.dateformat);
_focusNode.unfocus();
}
...
Widget code https://github.com/duchdtran/web_date_picker/blob/master/lib/src/web_date_picker.dart#L98
Could anybody provide example how to get button work to set widget value?
I experienced similar problems and solved them desribed as below.
It seems WebDatePicker does not process the value change. Try putting it in its own StatelessWidget:
class MyWebDatePicker extends StatelessClass {
final DateTime dt;
var ctrl = Get.find<SearchFormController>();
MyWebDatePicker(this.dt);
Widget build(BuildContext context) {
return WebDatePicker(
initialDate: dt,
onChange: (value) {
if (value != null) {
ctrl.endDate.value = value;
print('onChange event: ${value}');
}
},
);
}
}
Then, call it like given below. This code looks unusual, but it forces Obx to call MyWebDatePicker with the current DateTime value:
Obx(() => SizedBox(
width: 160,
child: MyWebDatePicker(ctrl.endDate.value)
)
)
i'm trying to pull new data from firebase cloud firestore and rebuild the widget on onPress of floating action button. i'm not sure how to rebuild the whole widget. Tried to call getList from the onPressed and setState() but still not rebulding widget evening nameList was updated.
class MyList extends StatefulWidget {
static const String id = 'test';
#override
_MyListState createState() => _MyListState();
}
class _MyListState extends State<MyList> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('TEST'),),
body: MainList(),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.refresh),
backgroundColor: Colors.teal,
onPressed: () {
}),
);
}
}
class MainList extends StatefulWidget {
#override
_MainListState createState() => _MainListState();
}
class _MainListState extends State<MainList> {
List<Test> nameList = [];
#override
void initState() {
super.initState();
getList();
}
getList() async {
final _name = await
Firestore.instance.collection('test').getDocuments();
nameList.clear();
for (var name in _name.documents) {
Test addName = new Test(
name.data['name'],
);
nameList.add(addName);
setState(() {});
}
}
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: nameList.length,
itemBuilder: (BuildContext context, int index) {
return Container(
child: Text(nameList[index].name),
);
});
}
}
Once a widget is created initState isn't called again. So your listview is still reflecting the old data.
You could getList in the onPressed which would then update your nameList. You could then pass this nameList to MainList.
class MyList extends StatefulWidget {
static const String id = 'test';
#override
_MyListState createState() => _MyListState();
}
class _MyListState extends State<MyList> {
List<Test> nameList = [];
getList() async {
final _name = await
Firestore.instance.collection('test').getDocuments();
nameList.clear();
for (var name in _name.documents) {
Test addName = new Test(
name.data['name'],
);
nameList.add(addName);
setState(() {});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('TEST'),),
body: MainList(nameList: nameList),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.refresh),
backgroundColor: Colors.teal,
onPressed: () {
getList();
}),
);
}
}
Your MainList widget would then look like:
class MainList extends StatefulWidget {
final List nameList;
MainList({this.nameList});
#override
_MainListState createState() => _MainListState();
}
class _MainListState extends State<MainList> {
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: nameList.length,
itemBuilder: (BuildContext context, int index) {
return Container(
child: Text(nameList[index].name),
);
});
}
}
Just calling setState() is not enough. You'll have to tell Dart what you are going to set. Sample code :
setState ( ()=> nameList = _fetchedList ) ;
In the above code, the variable nameList is assigned within setState().
In your code, you've two options.
Option 1 :
setState(() {
nameList.clear();
for (var name in _name.documents) {
Test addName = new Test(
name.data['name'],
);
nameList.add(addName);
}
});
Or option 2, better way, use for loop to add in the data in another list and use setState with one line as below :
List<Test> _fetchedList ;
for (var name in _name.documents) {
Test addName = new Test(
name.data['name'],
);
_fetchedList.add(addName);
}
setState( ()=> nameList = _fetchedList ) ;