Flutter Refresh List From API - flutter

i have a GET function in my flutter code and everytime i add a new item to the list. the list doesn't refresh and won't display the newly added item unless i refresh the whole page.
this is my POST method :
Future<http.Response> ajoutFournisseur(
String numeroFournisseur,
String addressFournisseur,
String matriculeFiscaleFournisseur,
String raisonSocialeFournisseur,
String paysFournisseur,
String villeFournisseur,
double timberFiscaleFournisseur) async {
List fournisseurs = [];
final response = await http.post(
Uri.parse('http://127.0.0.1:8000/api/fournisseur/'),
headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8',
},
body: jsonEncode(<String, dynamic>{
'tel': numeroFournisseur,
'adresse': addressFournisseur,
'mf': matriculeFiscaleFournisseur,
'raisonSociale': raisonSocialeFournisseur,
'pays': paysFournisseur,
'ville': villeFournisseur,
'timberFiscale': timberFiscaleFournisseur,
}),
);
if (response.statusCode == 200) {
return fournisseurs = jsonDecode(response.body);
} else {
throw Exception('Erreur base de données!');
}
}
Future<dynamic> future;
and this is code of the button to confirm :
ElevatedButton(
onPressed: (() {
if (_formKey.currentState.validate()) {
// If the form is valid, display a snackbar. In the real world,
// you'd often call a server or save the information in a database.
setState(() {
future = ajoutFournisseur(
numeroFournisseur.text,
addressFournisseur.text,
matriculeFiscaleFournisseur.text,
raisonSocialeFournisseur.text,
paysFournisseur.text,
villeFournisseur.text,
double.parse(timberFiscaleFournisseur.text));
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Ajout en cours')),
);
}
}), ...
and this is my GET methos to fetch the items from the list :
fetchFournisseurs() async {
final response =
await http.get(Uri.parse('http://127.0.0.1:8000/api/fournisseur'));
if (response.statusCode == 200) {
var items = jsonDecode(response.body);
setState(() {
fournisseurs = items;
print(fournisseurs[0]['raisonSociale']);
});
} else {
throw Exception('Error!');
}
}
.
.
.
for (var i = 0; i < fournisseurs.length; i++)
Card(
child: Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
ListTile(
title: Text(fournisseurs[i]['raisonSociale']), ...
how can i refresh the list everytime i add a new item without refreshing the whole page ?

I think you first of all need to learn some Flutter good practices.
For example, don't put your logic into the ElevatedButton, set it into a separate Widget function like below :
class Test extends StatefulWidget {
const Test({ Key? key }) : super(key: key);
#override
State<Test> createState() => _TestState();
}
class _TestState extends State<Test> {
Future<void> _handleAjout() async {
if (_formKey.currentState.validate()) {
// First, check your request succeed
try {
var fournisseurs = await ajoutFournisseur(
numeroFournisseur.text,
addressFournisseur.text,
matriculeFiscaleFournisseur.text,
raisonSocialeFournisseur.text,
paysFournisseur.text,
villeFournisseur.text,
double.parse(timberFiscaleFournisseur.text));
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Ajout en cours')),
);
// Will only update state if no error occured
setState(() => future = fournisseurs);
}
on Exception catch(e) {
// Always make sure the request went well
print("error");
}
}
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: (() {
_handleAjout();
}),
)
}
}
And by awaiting POST result, then you can tell your Widget to fetch data and refresh the list by making :
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: (() {
_handleAjout().then(() => fetchFournisseurs());
}),
)
}
The .then function tells Flutter to execute the code contained in the () => myCallbackFunction() only if the previous asynchronous function went well.
By the way, you should always check if your content looks like what you expected before calling setState and set data to your variables :)

Related

How to add json to an autocomplete widget in flutter

Im trying to pass the data from an API to a list so that I can view it in the Autocomplete widget but I keep getting errors. I tried below code.
This is the code I have tried which passes data to the autocomplete as instance of 'Bus'
GetBuilder<BusesListController>(
init: BusesListController(),
builder: (_) {
_.viewPartners();
return DottedBorder(
child: Padding(
padding:
const EdgeInsets.only(left: 8.0),
child: Autocomplete<Bus>(
optionsBuilder: (TextEditingValue
textEditingValue) {
List<Bus> partnercos = [];
partnercos = _.partners.value as List<Bus>;
// (_.partners).map((value) => Bus.fromJson(value as Map<String, dynamic>)).toList();
print(partnercos);
return partnercos
.where((bus) => bus.company!
.toLowerCase()
.contains(textEditingValue
.text
.toLowerCase()))
.toList();
},
)),
);
}),
I also tried passing _.partners directly but it doesn't work either
Other fix I tried is passing _.partners instead of _.partners. Value above which invokes errors in arena.dart in void _tryToResolveArena which shows that state. Members.length == 1 hence scheduleMicrotask(() => _resolveByDefault(pointer, state));
Contoller code
class BusesListController extends GetxController {
var partners = [].obs;
var isLoaded = false.obs;
final loginController = Get.put(LoginController());
Future<void> viewPartners() async {
final token = loginController.rxToken.value;
var headers = {
'Authorization': 'Bearer $token'
};
try {
var url =
Uri.parse(ApiEndPoints.baseUrl + ApiEndPoints.endpoints.listBusAdmin);
http.Response response = await http.get(url, headers: headers);
if (response.statusCode == 200) {
final json = jsonDecode(response.body);
partners. Value =
(json as List).map((json) => Bus.fromJson(json)).toList();
isLoaded.value = true;
} else {
throw jsonDecode(response.body)["Message"] ?? "Unknown Error Occured";
}
} catch (error) {
// Get.snackbar('Error', error.toString());
}
}
#override
void onInit() {
super.onInit();
viewPartners();
}
}
I am able to print the response so I know the api works but I'm having problems with passing partners list into the autocomplete

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

Flutter - onStepContinue called automatically on build

I'm using Stepper widget to make a form for profile creation. In the onStepContinue method, if its the last step I put the function call for sending data to backend and added the navigation route to home page to its .whenComplete method.
body: Stepper(
type: StepperType.horizontal,
currentStep: _activeCurrentStep,
steps: stepList(),
onStepContinue: () async {
final isLastStep = _activeCurrentStep == stepList().length - 1;
if (isLastStep) {
final authenticationNotifier =
Provider.of<AuthenticationNotifier>(context, listen: false);
var userEmail =
await authenticationNotifier.fetchUserEmail(context: context);
var profileName = profileNameController.text;
var profileBio = profileBioController.text;
await profileNotifier(false)
.createProfile(
context: context,
profileDTO: ProfileDTO(
useremail: userEmail,
profile_name: profileName,
profile_bio: profileBio,
))
.whenComplete(
() => Navigator.of(context).popAndPushNamed(HomeRoute));
} else if (_activeCurrentStep < (stepList().length - 1)) {
setState(() {
_activeCurrentStep += 1;
});
}
},
onStepCancel: _activeCurrentStep == 0
? null
: () {
setState(() {
_activeCurrentStep -= 1;
});
},
onStepTapped: (int index) {
setState(() {
_activeCurrentStep = index;
});
},
),
The stepper widget is in a page/scaffold of its own. Its loaded from onPressed of a button in authview.dart file.
onPressed: () {
authenticationNotifier.signup(
context: context,
useremail: signupEmailController.text,
userpassword: signupPasswordController.text);
Navigator.of(context)
.pushNamed(ProfileCreationRoute);
},
The problem is that as soon as I press the sign up button in authview the stepper page shows up for a fraction of a second and loads the homepage without letting me create the profile. I need it to just show the stepper and go to homepage only after I fill the profile details and click submit.
I thought .whenComplete would be called only when the button is pressed and its parent function finishes its work, and in this case I guess the problem is somehow with the stepper widget itself.
I also added && profileNameController.text.isNotEmpty in the if (isLastStep) condition but it doesn't work. Clicking on sign up button is bypassing the stepper widget and going to homeview as soon as stepper finishes building itself.
I don't understand what's going on. Please help.
EDIT
The createProfile function in notifier is
class ProfileNotifier extends ChangeNotifier {
final ProfileAPI _profileAPI = ProfileAPI();
Future createProfile({
required BuildContext context,
required ProfileDTO profileDTO,
}) async {
try {
await _profileAPI.createProfile(profileDTO);
} catch (e) {
print(e.toString());
}
}
}
And the API call that sends data to node backend is
class ProfileAPI {
final client = http.Client();
final headers = {
'Content-type': 'application/json',
'Accept': 'application/json',
"Access-Control-Allow-Origin": "*",
};
// Create new Profile
Future createProfile(ProfileDTO profileDTO) async {
final String subUrl = "/profile/create/${profileDTO.useremail}";
final Uri uri = Uri.parse(APIRoutes.BaseURL + subUrl);
try {
final http.Response response = await client.post(uri,
body: jsonEncode(profileDTO.toJson()), headers: headers);
final statusCode = response.statusCode;
final body = response.body;
if (statusCode == 200) {
return body;
}
} catch (e) {
print(e.toString());
}
}
}
EDIT 2
On changing whenComplete to then the linter shows this error.
The argument type 'Future<Object?> Function()' can't be assigned to the parameter type 'FutureOr<dynamic> Function(dynamic)'. dart(argument_type_not_assignable)
What to do? Please help
So from what is see is you are using the whencomplete, but the reason that's happening is the when complete will run every time either its and failure or success. So what i think is you should be using the then Method instead on whencomplete
which will only run in success condition.

Flutter where to put http.get

I am making lecture room reservation system.
class SearchView2 extends StatefulWidget {
#override
_SearchViewState2 createState() => _SearchViewState2();
}
class _SearchViewState2 extends State<SearchView2> {
String building = Get.arguments;
List data = [];
String roomID = "";
int reserved = 0;
int using = 0;
Future<String> getData() async {
http.Response res = await http.get(Uri.parse(
"https://gcse.doky.space/api/schedule/classrooms?bd=$building"));
http.Response res2 = await http.get(Uri.parse(
"https://gcse.doky.space/api/reservation/currtotal?bd=$building&crn=$roomID"));
reserved = jsonDecode(res2.body)["reserved"];
using = jsonDecode(res2.body)["using"];
this.setState(() {
data = jsonDecode(res.body)["result"];
});
return "success";
}
#override
void initState() {
super.initState();
this.getData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('강의실 선택')),
body: new ListView.builder(
itemCount: data == null ? 0 : data.length,
itemBuilder: (BuildContext context, int index) {
roomID = data[index];
return new Card(
child: ListTile(
onTap: () async {}, title: Text(data[index] + " " + reserved)),
);
},
),
);
}
}
I want to get 'using' and 'reserved' data and print them in the list view.
But roomID is in Listview
I want data[index] as roomID but with my code roomID will be null, so it won't print the result.
Where should I move http.Response res2? (not res)
Or is there other way to get using and reserved data in the listview?
First of all, you have a single building and multiple rooms in that building. So, fetching a building data along with the data of all it's rooms together will take too much time.
Instead, you can break it into two parts.
For fetching Building data,
Future<List<String>> getData() async {
http.Response res = await http.get(Uri.parse("https://gcse.doky.space/api/schedule/classrooms?bd=$building"));
return (jsonDecode(res.body)["result"] as List)
.map<String>((e) => e.toString())
.toList();
}
Then, for fetching each room data, Here you have to pass roomID.
Future<Map<String, dynamic>> getRoomData(String roomID) async {
http.Response res2 = await http.get(Uri.parse("https://gcse.doky.space/api/reservation/currtotal?bd=$building&crn=$roomID"));
return {
'reserved': jsonDecode(res2.body)["success"]["reserved"],
'using': jsonDecode(res2.body)["success"]["using"],
};
}
Now, you can use FutureBuilder widget to build something that depends on fetching data asynchronously.
You also don't need a StatefulWidget since you are using FutureBuilder and can remove all unnecessary local variables you have defined.
Here is the full working code. PasteBin Working Code.
Just replace your entire SearchView2 code with the code in the link.
This is the output.

Riverpod FutureProvider keeps on firiging again and again

I am using Riverpod's FutureProvider with family. The FutureProvider keeps on running again and again. It shows the loading dialog only. Also the hot reload stops working. FutureProvider is working fine without family. Please help in finding what's wrong.
final ephemerisProvider =
Provider((ref) => ApiService("https://localhost"));
final ephemerisFutureProvider = FutureProvider.family
.autoDispose<EpheModel, Map<String, dynamic>>((ref, data) async {
var response = await ref.read(ephemerisProvider).getData(data);
print(EpheModel.fromJSON(response));
return EpheModel.fromJSON(response);
});
class Kundlis extends ConsumerWidget {
static const routeName = "/kundlis";
#override
Widget build(BuildContext context, ScopedReader watch) {
final AsyncValue<EpheModel> kundlis = watch(ephemerisFutureProvider({}));
return Scaffold(
appBar: AppBar(
title: Text("Kundlis"),
),
drawer: AppDrawer(),
body: kundlis.when(
data: (kundli) => Center(child: Text(kundli.toString())),
loading: () => ProgressDialog(message: "Fetching Details..."),
error: (message, st) =>
CustomSnackBar.buildErrorSnackbar(context, '$message')));
}
}
class ApiService {
final String url;
ApiService(this.url);
Future<Map<String, dynamic>> getData(Map<String, dynamic> data) async {
try {
http.Response response = await http.post(url + "/ephe",
headers: <String, String>{'Content-Type': 'application/json'},
body: jsonEncode(data));
if (response.statusCode == 200) {
return data;
} else {
throw Exception("Error Fetching Details");
}
} on SocketException {
throw Exception("No Internet Connection");
} on HttpException {
throw Exception("Error Fetching Details");
}
}
}
{} != {}. Because of .family, you are creating a completely new provider every time you call watch(ephemerisFutureProvider({})). To select a previously-built provider via family, you must pass an identical value. And {} is never identical to {}, guaranteed. :)