Flutter: auto complete widget from reading items from text file - flutter

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!

Related

Flutter, how to call a function inside Stateful Widget from a returned 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,
);
}

How to call dynamic method in widget - flutter

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
}

Flutter async methods for widget initialize

Let's say I create a new screen team_screen which is the first parent of the tree.
Now for my team screen there are many widgets, some of theme have their own request, I want to show loader until every widget/request finished and ready.
I thought on 2 approaches.
All the requests are executed in team_screen with future builder and I pass the props to my widgets by demand.
Every widget with request get function that get executed in the async function in the initState function, then in my parent I make to every widget state parameter that is equal to true by the function I passed and when all is don't I stop the loader.
To sum up my problem is how to maintain a widget with many children and requests and showing one loader for entire page, making all the request on same widget? Pass isInitialize function to every widget?.
Which approach is better and if there are more approaches, I would like to hear.
Thank you for your help
Example for the second approach:
import 'package:flutter/material.dart';
import 'package:info_striker/locator.dart';
import 'package:info_striker/models/fixture/fixture.dart';
import 'package:info_striker/models/odds/bookmaker.dart';
import 'package:info_striker/models/synced-team/synced_team.dart';
import 'package:info_striker/services/fixture_service.dart';
import 'package:info_striker/utils/date_utilities.dart';
class TeamNextMatch extends StatefulWidget {
Function isInitialized;
SyncedTeam team;
TeamNextMatch({
Key? key,
required this.isInitialized,
required this.team,
}) : super(key: key);
#override
State<TeamNextMatch> createState() => _TeamNextMatchState();
}
class _TeamNextMatchState extends State<TeamNextMatch> {
Fixture? _fixture;
Bookmaker? _matchResult;
bool _isInitialized = false;
#override
void initState() {
super.initState();
init();
}
init() async {
final response = await locator<FixturesService>().getData(widget.team.id);
if (response != null) {
setState(() {
_fixture = Fixture.fromMap(response["fixture"]);
_matchResult = Bookmaker.fromMap(response["matchResultOdds"]);
});
}
widget.isInitialized(true);
}
#override
Widget build(BuildContext context) {
String? _date;
bool show = _fixture != null && _matchResult != null;
_fixture != null ? "${DateUtilities.getShortDateString(_fixture!.date)}, ${DateUtilities.getTimeString(_fixture!.date)}" : null;
return show
? Column(
children: [
Text(_fixture?.league?["name"]),
if (_date != null) Text(_date),
],
)
: const SizedBox();
}
}
You can show loader as described below -
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_application_1/data_model.dart';
import 'package:http/http.dart' as http;
class APiTest extends StatefulWidget {
const APiTest({Key? key}) : super(key: key);
#override
_APiTestState createState() => _APiTestState();
}
class _APiTestState extends State<APiTest> {
final String _url = "https://jsonplaceholder.typicode.com/todos/";
bool _isLoading = true;
final List<DataModel> _allData = [];
#override
void initState() {
super.initState();
_initData().then((value) {
setState(() {
_isLoading = false;
});
});
}
Future<void> _initData() async {
final response = await http.get(Uri.parse(_url));
final List res = jsonDecode(response.body);
res.forEach((element) {
_allData.add(DataModel.fromJson(element));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Loading Demo"),
),
body: Stack(
children: [
ListView.separated(
itemCount: _allData.length,
controller: ScrollController(),
separatorBuilder: (_, __) => const SizedBox(height: 10),
itemBuilder: ((context, index) {
return ListTile(
tileColor: Colors.grey[200],
title: Text(_allData[index].title!),
subtitle: Text(_allData[index].id.toString()),
);
}),
),
if (_isLoading)
const Center(
child: CircularProgressIndicator(),
)
],
),
);
}
}

How do I turn a Future <List> from Firestore into widgets with Flutter?

I know I'm successfully fetching the data from the DB because I can print it out in fetchFavorites() but I'm not dealing with the List correctly in _getFavesState because my result is:
type List〈dynamic〉 is not a subtype of type 'FutureOr<List 〈String〉>'
So how do I actually create widgets from my data? Code:
Future<List<String>> fetchFavorites() async {
FirebaseFirestore firestore = FirebaseFirestore.instance;
final user = FirebaseAuth.instance.currentUser;
final userData = await firestore.collection('users').doc(user.uid).get();
var faves = userData.get("favorites");
return faves;
}
class GetFaves extends StatefulWidget {
#override
_GetFavesState createState() => _GetFavesState();
}
class _GetFavesState extends State<GetFaves> {
Widget build(BuildContext context) {
Future<List> favoritesList;
favoritesList = fetchFavorites();
return Column(children: [
favoritesList == null
? Text('No Favorites')
: FutureBuilder(
future: favoritesList,
builder: (context, snapshot) {
if (snapshot.hasData) {
print("has data");
return Container(
child: ListView.builder(
itemCount: snapshot.data.length,
scrollDirection: Axis.horizontal,
itemBuilder: (BuildContext context, int index) {
return Text('${snapshot.data[index].title}');
}));
} else if (snapshot.hasError) {
return Text('${snapshot.error.toString()}');
} else {
return CircularProgressIndicator();
}
}),
]);
}
}
EDIT:
I don't think it is a straight ahead list of strings. It is some kind of record that looks like a serialized JSON: {CoverUrl : https://...., Title: some title, cid: something} So the error makes sense. Not sure how that changes the solution.
A few remarks on your code:
You don't need a StatefulWidget since the data is managed by your FutureBuilder
favoritesList == null will always be false since it's a Future<List<String>>
In snapshot.data[index].title, snapshot.data[index] is a String, what is this title?
You can remove the Container, it just has a child and is therefore useless.
I think we can simplify a bit:
(I left out the Firestore part since you tell us it works fine. Though, are you sure userData.get("favorites") returns a String? If so, cast it to a String to match your Future<List<String>> signature)
Full source code
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: HomePage(),
),
);
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: GetFaves(),
);
}
}
class GetFaves extends StatefulWidget {
#override
_GetFavesState createState() => _GetFavesState();
}
class _GetFavesState extends State<GetFaves> {
Future<List<String>> _fetchFavorites() async {
return List.generate(10, (index) => 'Favorite $index');
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<String>>(
future: _fetchFavorites(),
builder: (context, snapshot) {
print(snapshot);
if (snapshot.hasData) {
print(snapshot.data);
return ListView(
scrollDirection: Axis.horizontal,
children: snapshot.data.map((favorite) => Text(favorite)).toList(),
);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
} else {
return CircularProgressIndicator();
}
},
);
}
}
Update : Album from JSON Data
Your Albums are retrieved as JSON Data from Firestore: Here is an example:
[
{'cid': '593312', 'title': 'porttitor', 'coverUrl': 'https://source.unsplash.com/640x480'},
{'cid': '910654', 'title': 'auctor', 'coverUrl': 'https://source.unsplash.com/640x480'},
{'cid': '276961', 'title': 'nullam', 'coverUrl': 'https://source.unsplash.com/640x480'},
{'cid': '413021', 'title': 'rhoncus', 'coverUrl': 'https://source.unsplash.com/640x480'},
{'cid': '299898', 'title': 'posuere', 'coverUrl': 'https://source.unsplash.com/640x480'}
]
Such data in Dart is usually defined as a List<Map<String, dynamic>>.
In this solution, we will use the freezed package (depending on json_serializable package) to generate the Domain Class Album. The code of this class is generate in your_file.freezed.dart for the Immutable Domain Class and your_file.g.dart for the from/toJson functionality. To generate these two files, you will need to also install the Dev Dependency build_runner and run the following command at the root of your project:
flutter pub run build_runner watch --delete-conflicting-outputs
Once everything is setup, your _fetchFavorites() will become:
Future<List<Album>> fetchFavorites() async {
FirebaseFirestore firestore = FirebaseFirestore.instance;
final user = FirebaseAuth.instance.currentUser;
final userData = await firestore.collection('users').doc(user.uid).get();
var faves = userData.get("favorites");
return faves.map((jsonData) => Album.fromJson(jsonData)).toList();
}
Note: This snippet has not been tested
Full source code using dummy Data
Note: the faker package is used to generate random dummy data.
import 'package:faker/faker.dart';
import 'package:flutter/material.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part '66473551.future.freezed.dart';
part '66473551.future.g.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
home: HomePage(),
),
);
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: FavoriteList(),
);
}
}
class FavoriteList extends StatelessWidget {
Future<List<Album>> _fetchFavorites() async {
return dummyData.map((jsonData) => Album.fromJson(jsonData)).toList();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Album>>(
future: _fetchFavorites(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView(
children: snapshot.data
.map((favorite) => AlbumWidget(album: favorite))
.toList(),
);
} else if (snapshot.hasError) {
return Text(snapshot.error.toString());
} else {
return CircularProgressIndicator();
}
},
);
}
}
class AlbumWidget extends StatelessWidget {
final Album album;
const AlbumWidget({
Key key,
this.album,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return ListTile(
leading: Image.network(album.coverUrl),
title: Text(album.title),
subtitle: Text(album.cid),
);
}
}
#freezed
abstract class Album with _$Album {
const factory Album({String cid, String title, String coverUrl}) = _Album;
factory Album.fromJson(Map<String, dynamic> json) => _$AlbumFromJson(json);
}
final faker = new Faker();
final List<Map<String, dynamic>> dummyData = List.generate(
10,
(index) => {
'cid': faker.randomGenerator.integer(999999).toString(),
'title': faker.lorem.word(),
'coverUrl': faker.image.image(),
},
);
You do something like this
class GetFaves extends StatefulWidget {
#override
_GetFavesState createState() => _GetFavesState();
}
class _GetFavesState extends State<GetFaves> {
#override
void initState(){
super.initState();
fetchFavourites();
}
#override
Widget build(BuildContext context) {
List<Widget> widgetList = [SizedBox(height:50),CircularProgressIndicator()];
return Column(
children:widgetList,
)
}
void fetchFavorites() async {
widgetList = [SizedBox(height:50),CircularProgressIndicator()];
FirebaseFirestore firestore = FirebaseFirestore.instance;
final user = FirebaseAuth.instance.currentUser;
final userData = await firestore.collection('users').doc(user.uid).get();
var faves = userData.get("favorites");
widgetList = [];
//Build the list of Containers here with a for loop and store it in your variable widgetList. I could not understand what you were building so I left that part for you
setState((){});
}
}
What I am basically doing is showing a loading animation in the start and when the documents have been loaded using the function from initState and you have made the widgets, I rebuild the class with a list of new Widgets
What was happening was that I was fetching from the database a List of type dynamic, which in this case is some kind of deserialized JSON looking text. When I changed the function signature of my function fetchFavorites to this, the build method of my calling class started working:
Future<List<dynamic>> fetchFavorites() async {
...
}
The elements of the returned records are accessed as a 2-dimensional array, so:
record[index]["title"]
and so on.

Return async value to initState()

I've the below code that is working fine, reading the csv data from url and printing the output:
import 'package:flutter/material.dart';
import 'dart:convert';
import 'dart:io';
import 'dart:async';
import 'package:csv/csv.dart';
void fetchUserData() async {
final request = await HttpClient().getUrl(Uri.parse(
'https://docs.google.com/spreadsheets/d/e/2PACX-1vQvf9tp4-fETDJbC-HRmRKvVFAXEAGO4lrYPpVeiYkB6nqqXdSs3CjX0eBMvjIoEeX9_qU6K2RWmzVk/pub?gid=0&single=true&output=csv'));
final response = await request.close();
List<List<dynamic>> rowsAsListOfValues;
await for (final csvString in response.transform(const Utf8Decoder())) {
rowsAsListOfValues =
const CsvToListConverter().convert(csvString);
}
print(rowsAsListOfValues);
}
class _MyHomePageState extends State<MyHomePage> {
#override
void initState() {
super.initState();
fetchUserData();
}
#override
Widget build(BuildContext context) { // ... // }
}
Instead of getting the output printed, I need it to be returned into a variable, which I can display in y widget, I tried to do it as below:
Future<List<List<dynamic>>> fetchUserData() async { /// change
final request = await HttpClient().getUrl(Uri.parse(
'https://docs.google.com/spreadsheets/d/e/2PACX-1vQvf9tp4-fETDJbC-HRmRKvVFAXEAGO4lrYPpVeiYkB6nqqXdSs3CjX0eBMvjIoEeX9_qU6K2RWmzVk/pub?gid=0&single=true&output=csv'));
final response = await request.close();
List<List<dynamic>> rowsAsListOfValues;
await for (final csvString in response.transform(const Utf8Decoder())) {
rowsAsListOfValues =
const CsvToListConverter().convert(csvString);
}
return rowsAsListOfValues; /// change
}
class _MyHomePageState extends State<MyHomePage> {
var rowsAsListOfValues; /// new
#override
void initState() {
super.initState();
rowsAsListOfValues = fetchUserData(); /// new
print(rowsAsListOfValues); /// new
}
#override
Widget build(BuildContext context) { // ... // }
}
But I got the output as I/flutter ( 7505): Instance of 'Future<List<List<dynamic>>>'
How can I fix it?
You need to switch from initState to didChangeDependency in this case. Because you need to await some process and you cant wait in initState. However you can wait like this
#override
void didChangeDependencies() async {
super.didChangeDependencies();
rowsAsListOfValues = await fetchUserData();
super.setState(() {}); // to update widget data
/// new
print(rowsAsListOfValues);
}
And this is the result
I/flutter (24313): [[vranches], [Dammam, 2], [Khobar, 3]]
You can wrap your code with Future.delayed() as given below.
#override
void initState() {
super.initState();
Future.delayed(Duration.zero,()async{
rowsAsListOfValues =await fetchUserData();
setState(() {});
print(rowsAsListOfValues); // this return correct value
});
print(rowsAsListOfValues); // this return null
}
Full Code
import 'package:flutter/material.dart';
import 'dart:convert';
import 'dart:io';
import 'dart:async';
import 'package:csv/csv.dart';
void main() {
runApp(App());
}
class App extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: HomePage(title: 'Flutter Demo Home Page'),
);
}
}
class HomePage extends StatefulWidget {
HomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_AppState createState() => _AppState();
}
Future<List<List<dynamic>>> fetchUserData() async {
final request = await HttpClient().getUrl(Uri.parse(
'https://docs.google.com/spreadsheets/d/e/2PACX-1vQvf9tp4-fETDJbC-HRmRKvVFAXEAGO4lrYPpVeiYkB6nqqXdSs3CjX0eBMvjIoEeX9_qU6K2RWmzVk/pub?gid=0&single=true&output=csv'));
final response = await request.close();
List<List<dynamic>> rowsAsListOfValues;
await for (final csvString in response.transform(const Utf8Decoder())) {
rowsAsListOfValues =
const CsvToListConverter().convert(csvString);
}
return rowsAsListOfValues;
}
class _AppState extends State<HomePage> {
var rowsAsListOfValues;
#override
void initState() {
super.initState();
Future.delayed(Duration.zero, () async {
rowsAsListOfValues = await fetchUserData();
setState(() {});
print(rowsAsListOfValues);
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'$rowsAsListOfValues',
),
],
),
),
);
}
}
The initState method is synchronous, and does not support async. I recommend the use of FutureBuilder, but you can also move the code to an async function.
FutureBuilder
import 'package:flutter/material.dart' show
Widget, FutureBuilder, AsyncSnapshot
;
class _MyHomePageState extends State<MyHomePage> {
static Future<void> fetchUserData() {
return Future().delayed(
Duration(seconds: 10),
() => 'loaded'
);
}
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: Future.wait([
fetchUserData()
]),
builder: (
BuildContext context,
AsyncSnapshot snapshot
) {
if (snapshot.hasData) {
return Text(snapshot.data);
}
return Text('loading...');
}
);
}
}
Async function
#override
void initState () {
super.initState();
(() async {
rowsAsListOfValues = await fetchUserData();
print(rowsAsListOfValues);
})();
}
OR
#override
void initState() {
super.initState();
initLoad();
}
void initLoad() async {
rowsAsListOfValues = await fetchUserData();
print(rowsAsListOfValues);
}
I feel more relaxed when using then() with async functions. You can try this:
fetchUserData().then((value) {
setState(() {
rowsAsListOfValues = value;
});
});
Or you can use await like this.
#override
void initState() async {
super.initState();
rowsAsListOfValues = await fetchUserData();
print(rowsAsListOfValues);
}