How to render my widget after data loading completed in Flutter - flutter

What I'm trying to do
After sign in, showing HomeScreen which has a listview. This listview is showing data coming from an API. So, it takes a little bit time to load. I want to render my HomeScreen after loading is completed an my listview is full of data.
What I've already done
I have an api helper class that does api works. It has a static method which is getting data for my listview and returning a list. This is my getFixtureData method.
static Future<List<Fixtures>> getFixtureData() async {
Map<String, String> queryParameters = {
'league': '78',
'next': '20',
};
http.Response response = await http.get(
getUrl('fixtures', queryParameters),
headers: requestHeaders,
);
print(response.body);
if (response.statusCode == 200) {
String data = response.body;
List<dynamic> result = jsonDecode(data)['response'];
for (int i = 0; i < result.length; i++) {
Fixtures fixture = Fixtures();
fixture.leagueID = jsonDecode(data)['response'][i]['league']['id'];
fixture.country = jsonDecode(data)['response'][i]['league']['country'];
fixture.leagueName = jsonDecode(data)['response'][i]['league']['name'];
fixture.fixtureID = jsonDecode(data)['response'][i]['fixture']['id'];
//get Odds to match with fixtures by fixtureID
await getOddsData(fixture.fixtureID);
fixture.dateTime =
DateTime.parse(jsonDecode(data)['response'][i]['fixture']['date']);
fixture.homeTeam =
jsonDecode(data)['response'][i]['teams']['home']['name'];
fixture.awayTeam =
jsonDecode(data)['response'][i]['teams']['away']['name'];
fixture.status =
jsonDecode(data)['response'][i]['fixture']['status']['long'];
fixture.homeGoals = jsonDecode(data)['response'][i]['goals']['home'];
fixture.awayGoals = jsonDecode(data)['response'][i]['goals']['away'];
fixture.htScoreHome =
jsonDecode(data)['response'][i]['score']['halftime']['home'];
fixture.htScoreAway =
jsonDecode(data)['response'][i]['score']['halftime']['away'];
fixture.ftScoreHome =
jsonDecode(data)['response'][i]['score']['fulltime']['home'];
fixture.ftScoreAway =
jsonDecode(data)['response'][i]['score']['fulltime']['away'];
if (oddsList.length > 0) {
for (int j = 0; j < oddsList.length; j++) {
if (oddsList[j].fixtureID == fixture.fixtureID) {
fixture.homeOdds = oddsList[j].homeOdds;
fixture.drawOdds = oddsList[j].drawOdds;
fixture.awayOdds = oddsList[j].awayOdds;
fixture.bookmakerName = oddsList[j].bookmakerName;
FootballApi.fixtureList.add(
fixture); // this line must be here. If there is no odds of that match, it should not be in fixtureList
}
}
}
}
} else {
print('statusCode: ' + response.statusCode.toString());
}
return FootballApi.fixtureList;
}
I'm calling this method in my stateful widget(HomeScreen) which have a listview that I feed it with the list getFixtureData method returned. I'm calling it in initState of HomeScreen.
This is the relevant code.
class HomeScreen extends StatefulWidget {
static String id = 'home_screen';
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
int gameIndex = 0;
String gameTitle = 'Soccer';
int selectedCategoryIndex = 0;
List<Fixtures> fixtureList = List<Fixtures>();
#override
void initState() {
// TODO: implement initState
super.initState();
getFixture();
}
Future<List<Fixtures>> getFixture() async {
fixtureList = await FootballApi.getFixtureData();
return fixtureList;
}
And this part is the where I pass the data to listview in HomeScreen.
Stack(children: [
AppListView(
matchList: FootballApi.fixtureList,
//callback function brings the matchCounter value from ListView class
onChange: (value) {
setState(() {
matchCounter = value;
});
},
finalBetList: (value) {
setState(() {
betList = value;
});
},
),
The problem is; when user signs in, he is viewing my home screen with an empty listview. After a few seconds listview is loaded. I want to render HomeScreen after my listview loaded fully. What is the best way for that?

use FutureBuilder widget to build widgets after getting data from async functions;
FutureBuilder<List<Fixtures>>(
future: getFixture(),
builder: (context, snapshot) {
if (snapshot.hasData) {
List<Fixtures> yourResponseDataFromAsync = snapshot.data;
return AppListView(
matchList: yourResponseDataFromAsync,
//callback function brings the matchCounter value from ListView class
onChange: (value) {
setState(() {
matchCounter = value;
});
},
finalBetList: (value) {
setState(() {
betList = value;
});
},
);
}
return CircularProgressIndicator();
},
),

Related

Why below codes after "await" keyword are not execute?

The method goes to the line that calls the method that calls it, without moving to the lines after the await keyword
class WordAndMeaning extends StatelessWidget {
WordAndMeaning({super.key});
final viewModel = locator<HomeViewModel>();
#override
Widget build(BuildContext context) {
return Observer(
builder: (_) {
if (viewModel.userDailyRight == null) {
return const CircularProgressIndicator();
} else if (viewModel.userDailyRight == 0) {
return const Text(
"Out of your limit",
style: TextStyle(color: Colors.red, fontSize: 20),
);
} else if (viewModel.userDailyRight != 0 && !viewModel.isWordsPulled) {
viewModel.runPullWord();
if (viewModel.isStartedWordPulling) {
return const CircularProgressIndicator();
}
I run runPullWord after check userDailyRight and isWordsPulled at this stateless widget.
List<String> wordList = [
"pencil",
"headphone",
"bag",
"door",
"dress",
"bin",
];
final String _apiLink = "https://api.dictionaryapi.dev/api/v2/entries/en/";
#action
runPullWord() async {
isStartedWordPulling = true;
learnedWordKey = await pullLearnedWordsKeys();
wordAndMeanList = await pullWord();
setRandomWordIndexRightNow();
}
#action
Future<List<String>> pullLearnedWordsKeys() async {
return (await _firestore
.doc("students/$userID/learnedwords/learnedwords")
.get())
.data()!
.keys
.toList();
}
#action
Future<List<WordModel>> pullWord() async {
List<WordModel> localWordAndMeanList = [];
isWordsPulled = false;
try {
for (String word in learnedWordKey) {
wordList.remove(word);
}
for (int i = 0; i < wordList.length; i++) {
_response = await Dio().get(_apiLink +
wordList[
i]);
_pulledData = _response.data;
List<WordModel> currentWord =
((_pulledData as List).map((e) => WordModel.fromMap(e)).toList());
localWordAndMeanList.add(currentWord[0]);
}
isStartedWordPulling = false;
isWordsPulled = true;
return localWordAndMeanList;
} catch (e) {
debugPrint(e.toString());
return Future.error(e);
}
}
When the line with _response works here, the program goes back to the viewModel.runPullWord() line in the widget as soon as it comes to the await keyword and the pullWord method is not completed(_response is still null). In this way, the pullWord method is repeatedly executed.
This situation made me question my knowledge. Why did this situation occur? How can I solve this?

Riverpod future provider not rebuilding ui

My problem is that when I run the app, the data doesn't show up on the UI. The code below is rendered under a bottom navigation bar format which is a stateful widget. To my knowledge the below code should work (show data on the initial running of app).
The code works but the data is only shown when I press hot reload. I've tried everything that I know but it still doesn't show data when I start the app.
final imageControllerProvider = Provider((ref) {
return ImageController();
});
final mainScreenImages = FutureProvider<List<String>>((ref) async {
List<String> list = [];
list = await ref.watch(imageControllerProvider).getImages();
return list;
});
class ImageController{
Future<List<String>> getImages() async {
List<String> imageUrls = [];
try {
final Reference reference = _storage.ref().child("weed/");
reference.listAll().then((value) {
for (var element in value.items) {
element.getDownloadURL().then((e) => imageUrls.add(e));
}
});
} catch (e) {
print(e);
}
return imageUrls;
}
}
class GenerateImages extends ConsumerWidget {
const GenerateImages({super.key});
#override
Widget build(BuildContext context, WidgetRef ref) {
final imageList = ref.watch(mainScreenImages);
final double screenwidth = MediaQuery.of(context).size.width;
final double screenheight = MediaQuery.of(context).size.height;
return imageList.when(data: (data) {
return Text('$data');
}, error: (_, __) {
return const Scaffold(
body: Center(
child: Text("OOPS"),
),
);
}, loading: () {
return const Center(child: const CircularProgressIndicator());
});
}
}
I think the problem is because in getImages() you are not awaiting the results instead you are using the then() handler to register callbacks. Replace your getImages() function with this and try.
Future<List<String>> getImages() async {
List<String> imageUrls = [];
try {
final Reference reference = _storage.ref().child("weed/");
final value = await reference.listAll();
for (var element in value.items) {
final url = await element.getDownloadURL();
imageUrls.add(url);
}
} catch (e) {
print(e);
}
return imageUrls;
}
}

Is there a way to access data coming from BLE device from single file which keeps updating?

I want to access characteristic values of BLE from one dart file, What I am doing is that I am connecting the device from one activity and then sending the device info to all other activities. But to get values I have to write the same code again and again to all activities/dart files.
For example i am connecting device in an activity like this:
StreamBuilder<List<ScanResult>>(
stream: FlutterBlue.instance.scanResults,
initialData: [],
builder: (c, snapshot) => Column(
children: snapshot.data
.map(
(r) => ScanResultTile(
result: r,
onTap: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (context) {
r.device.connect();
print('DEVICE CONNECTED');
return BluetoothConnectedSuccess(device: r.device);
Here device: r.device is the device that i have connected to my Flutter App. Now if i want to display device data i have to initilaze these lines of code everytime i jump to any screen/activity:
class BluetoothConnectedSuccess extends StatefulWidget {
const BluetoothConnectedSuccess({Key key, this.device}) : super(key: key);
final BluetoothDevice device;
#override
_BluetoothConnectedSuccessState createState() =>
_BluetoothConnectedSuccessState();
}
class _BluetoothConnectedSuccessState extends State<BluetoothConnectedSuccess> {
// BLE
final String SERVICE_UUID = "4fafc201-1fb5-459e-8fcc-c5c9c331914b";
final String CHARACTERISTIC_UUID = "beb5483e-36e1-4688-b7f5-ea07361b26a8";
bool isReady;
Stream<List<int>> stream;
List<int> lastValue;
List<double> traceDust = List();
#override
void initState() {
super.initState();
isReady = false;
connectToDevice();
}
connectToDevice() async {
await widget.device.connect();
discoverServices();
}
discoverServices() async {
List<BluetoothService> services = await widget.device.discoverServices();
services.forEach((service) {
if (service.uuid.toString() == SERVICE_UUID) {
service.characteristics.forEach((characteristic) {
if (characteristic.uuid.toString() == CHARACTERISTIC_UUID) {
characteristic.setNotifyValue(!characteristic.isNotifying);
stream = characteristic.value;
print(stream);
lastValue = characteristic.lastValue;
print(lastValue);
setState(() {
isReady = true;
});
}
});
}
});
}
_dataParser(List<int> data) {
var value = Uint8List.fromList(data);
print("stream.value: $value"); // stream.value: [33]
var hr = ByteData.sublistView(value, 0, 1);
print("Heart rate: ${hr.getUint8(0)}");
return hr.getUint8(0); // Heart rate: 33
}
It's creating a lot of mess to write the same code again and again to the activities where BLE data is needed.
Is there a way to only call this connected device from a single file instead of initializing the same code in every activity?
This is the link to my repo for a look at what I am doing on every activity/screen with BLE device data.
Please help me out as I am new to Flutter. Thank you
Firstly learn basic of state management using Get you can refer to my code here what happens is every time something changes or upadtes it will immediate show in UI using specific Get Widgets like Obx and GetX , these widgets listen to changes in value which are marked with obs (observable).
for exmaple :
Obx(
() => ble.isScan.value ? LinearProgressIndicator() : SizedBox(),
),
this will observe the changes in isScan value .
class BleServices extends GetxController {
FlutterBlue blue = FlutterBlue.instance;
BluetoothDevice d;
var connectedDevices = List<BluetoothDevice>().obs;
var scanResults = List<ScanResult>().obs;
var bleState = BluetoothState.off.obs;
var isScan = false.obs;
var scanRequire = false.obs;
var bluetoothServices = List<BluetoothService>().obs;
var discoveringServices = false.obs;
var characteristics = List<BluetoothCharacteristic>().obs;
#override
void onInit() async {
super.onInit();
final perb = await Permission.bluetooth.status.isGranted;
final perL = await Permission.location.status.isGranted;
if (perb && perL) {
getConnectedDevices();
} else {
await Permission.bluetooth.request();
await Permission.location.request();
}
isScanning();
state();
}
isDiscovering() async {}
getConnectedDevices() async {
final connectedDevice = await blue.connectedDevices;
connectedDevices.value = connectedDevice;
AppLogger.print('connected devices : $connectedDevice');
if (connectedDevice.length == 0) {
scanRequire.value = true;
searchDevices();
}
return connectedDevice;
}
searchDevices() {
// AppLogger.print('pppppppppppppp');
blue
.scan(timeout: Duration(seconds: 20))
.distinct()
.asBroadcastStream()
.listen((event) {
AppLogger.print(event.toString());
scanResults.addIf(!scanResults.contains(event), event);
});
Future.delayed(Duration(seconds: 20), () {
blue.stopScan();
Get.showSnackbar(GetBar(
message: 'scan is finished',
));
});
}
isScanning() {
blue.isScanning.listen((event) {
AppLogger.print(event.toString());
isScan.value = event;
});
}
state() {
blue.state.listen((event) {
AppLogger.print(event.toString());
bleState.value = event;
});
}
}

How to Refresh ListView.Builder when data changes in background?

I have a ListView which gets data from API, the API call is first made from initState() and assigning to List<DataFromApi> dataFromApi = [];. After sometime am doing a background call again to the same API, Am getting the data and assigning to the same list dataFromApi in
setState(() {
dataFromApi = [];
for (DataFromApi td in resData.data) {
dataFromApi.add(td);
}
});
When logging it, the data changes, but UI not changing. How can I update the ListView?
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<DataFromApi> dataFromApi = [];
#override
void initState() {
loadData();
super.initState();
fetchFirstData();
initTimedDataCall();
}
initTimedDataCall() {
Timer.periodic(Duration(minutes: 5), (timer) {
setState(() {
timedCall();
});
});
}
fetchFirstData() async {
final response = await http.get(
Utils.getDataUrl,
);
if (response.statusCode == 200) {
final resData = DataListFromJson(response.body);
if (resData.status == 1) {
print(response.body);
setState(() {
dataFromApi = [];
for (DataFromApi td in resData.data) {
dataFromApi.add(td);
}
});
}
///
///
/// DOING SOME MORE THINGS IN FIRST CALL
///
///
}
}
timedCall() async {
final response = await http.get(
Utils.getDataUrl,
);
if (response.statusCode == 200) {
final resData = DataListFromJson(response.body);
if (resData.status == 1) {
print(response.body);
setState(() {
dataFromApi = [];
for (DataFromApi td in resData.data) {
dataFromApi.add(td);
}
});
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ListView.builder(
itemCount: dataFromApi.length,
itemBuilder: (context, index) {
return Text('${dataFromApi[index].title}');
}
),
);
}
}
Try calling setState after computation is done
timedCall() async {
final response = await http.get(
Utils.getDataUrl,
);
if (response.statusCode == 200) {
final resData = DataListFromJson(response.body);
if (resData.status == 1) {
print(response.body);
dataFromApi = [];
for (DataFromApi td in resData.data) {
dataFromApi.add(td);
}
setState(() {
});
}
}
}
use similar for other methods. This is working at my end

Correct way to call an api by provider in fflutter?

I have been trying to make a app in flutter where an api is called and data is updated in TextField
Used provider for state management, here is the code for it.
class ProfileProvider with ChangeNotifier {
var profileData;
String _url = "http://10.0.2.2:3000/api/v1/user/loggedin_user";
void getData() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var token = prefs.getString('token');
var data = await http.get(
_url,
headers: {
"accept": "application/json",
"content-type": "application/json",
'Token': token,
},
);
var infoOfPerson = json.decode(data.body);
profileData = new ProfileObject(
name: infoOfPerson['name'],
mobile: infoOfPerson['mobile'],
email: infoOfPerson['email'],
role: infoOfPerson['role'],
);
notifyListeners();
}
ProfileObject get profileInfo {
return profileData;
}
}
I am getting the data fine, now i have to show it in the UI, but sometime data is populated, sometime its not. Can someone please point me the right direction why this is happening.
Here is the code for UI.
class Profile extends StatefulWidget {
#override
_ProfileState createState() => _ProfileState();
}
class _ProfileState extends State<Profile> {
final emailController = TextEditingController(text: '');
final nameController = TextEditingController(text: '');
final mobileController = TextEditingController(text: '');
var _isInit = true;
#override
void didChangeDependencies() {
if (_isInit) {
final profileData = Provider.of<ProfileProvider>(context);
profileData.getData();
if (profileData.profileInfo != null) {
emailController.text = profileData.profileInfo.name;
nameController.text = profileData.profileInfo.email;
mobileController.text = profileData.profileInfo.mobile;
}
_isInit = false;
super.didChangeDependencies();
}
}
#override
Widget build(BuildContext context) {
final profileData = Provider.of<ProfileProvider>(context);
return Scaffold(
drawer: NavigationDrawer(),
body: profileData.profileInfo == null
? Center(
child: CircularProgressIndicator(),
)
: Builder(
builder: (context) => SingleChildScrollView(
child: Padding(.....
Below the padding, there is normal TextField, can someone tell me why the data is being populated sometime and sometime its coming empty, even I wrapped it with CircularProgressIndicator() and a check the notifyListeners(); is not working there. The loader is not being shown and data is not being loaded.
Thanks
for StatelessWidget.
Inside the build method use:
Future.microtask(() async {
context.read<SomeProvider>().fetchSomething();
});
For StatefulWidgets if you want to call it once. Do this inside the initState() or didChangeDependencies (better if the latter). This will be called at the end of the frame which means after the build or rendering finishes..
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
context.read<SomeProvider>().fetchSomething();
});
}
EDIT: WidgetsBinding will also work on build. I forgot on why I used microtask lol
i've created a function which called nextTick, i call it in initState and it works for now, but want to see others method
void nextTick(Function callback, [int milliseconds = 0]) {
Future.delayed(Duration(milliseconds: 0)).then((_) {
callback();
});
}
then use it like below
#override
void initState() {
super.initState();
nextTick((){
ProfileProvider profileProvider = Provider.of<ProfileProvider>(context);
profileProvider.getProfile();
});
}
Edit: i store couple of variables to manage them on ui, like isLoading, hasError and errorMessage. Here is my provider class
class ProfileProvider extends ChangeNotifier {
bool _hasError = false;
bool _isLoading = true;
String _errorMsg = '';
Profile _profileResponse;
bool get hasError => _hasError;
bool get isLoading => _isLoading;
String get errorMsg => _errorMsg;
Profile get profileResponse => _profileResponse;
Future<void> getProfile() async {
this.setLoading = true;
this.setError = false;
this.setErrorMsg = '';
try {
await dio.post('$api/p/view', data: {}).then((res) {
print(res.data);
_profileResponse = Profile.fromJson(jsonDecode(res.data));
print(_profileResponse.data);
notifyListeners();
}).whenComplete(() {
this.setLoading = false;
});
} catch (e) {
this.setError = true;
this.setErrorMsg = '$e';
}
this.setLoading = false;
}
set setError(bool val) {
if (val != _hasError) {
_hasError = val;
notifyListeners();
}
}
set setErrorMsg(String val) {
if (val != null && val != '') {
_errorMsg = val;
notifyListeners();
}
}
set setLoading(bool val) {
_isLoading = val;
notifyListeners();
}
}