How to get data from API before loading page [closed] - api

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
The community reviewed whether to reopen this question last month and left it closed:
Needs details or clarity Add details and clarify the problem by editing this post.
Improve this question
I am trying to get data from an API to show on screen. I created a function and call it from widget build function. I also try to call it from constructor, but it will call that function in the end, so list empty error comes.
I want to call the API before screen load and store data in the list.

You can do it by the below code:
First you have to Hit Api with a onPressed or onTap method within a Button like
onPressed: () {
getNutritionDetails(context,"2");
getDataFromMyApi(context);
},
Then make a function to make the code more clean and reader with any name, here i named the function as getDataFromMyApi
void getDataFromMyApi(BuildContext context) {
//Getting data from API and store it in String or Model or List whatever you required.
String myData;
//and then pass the data to your second activity
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
MyStatefulWidget(myData), // Your Api response myData is Passing to Stateful Widget
),
);
}
and then i made a Stateful Widget where you want to pass the data and wants it to load data before it is opened and there you can have your data before the page loads in myData var.
class MyStatefulWidget extends StatefulWidget {
String myData;
MyStatefulWidget(this.myData, {Key key}) : super(key: key);
#override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
#override
Widget build(BuildContext context) {
return Container(
height: double.infinity,
width: double.infinity,
child: Text(widget.myData),
);
}
}
OR
You Can Use Future Builder
Ques: What does Future Builder do?
Ans: It calls the future function to wait for the result, and as soon as it produces the result it calls the builder function where we build the widget.
class _ExampleState extends State<Example> {
#override
Widget build(BuildContext context) {
return FutureBuilder<String>(
future: downloadData(), // function where you call your api
builder: (BuildContext context, AsyncSnapshot<String> snapshot) { // AsyncSnapshot<Your object type>
if( snapshot.connectionState == ConnectionState.waiting){
return Center(child: Text('Please wait its loading...'));
}else{
if (snapshot.hasError)
return Center(child: Text('Error: ${snapshot.error}'));
else
return Center(child: new Text('${snapshot.data}')); // snapshot.data :- get your object which is pass from your downloadData() function
}
},
);
}
Future<String> downloadData()async{
// var response = await http.get('https://getProjectList');
return Future.value("Data download successfully"); // return your response
}
}

Related

Custom Result in Net 6 Minimal API

In ASP.NET Core 5 I had a custom Action Result as follows:
public class ErrorResult : ActionResult {
private readonly IList<Error> _errors;
public ErrorResult(IList<Error> errors) {
_errors = errors;
}
public override async Task ExecuteResultAsync(ActionContext context) {
// Code that creates Response
await result.ExecuteResultAsync(context);
}
}
Then on a Controller action I would have:
return new ErrorResult(errors);
How to do something similar in NET 6 Minimal APIs?
I have been looking at it and I think I should implement IResult.
But I am not sure if that is the solution or how to do it.
I have recently been playing around with minimal APIs and and working on global exception handling. Here is what I have come up with so far.
Create a class implementation of IResult
Create a constructor which will take an argument of the details you want going into your IResult response. APIErrorDetails is a custom implementation of mine similar to what you'd see in ProblemDetails in MVC. Method implementation is open to whatever your requirements are.
public class ExceptionAllResult : IResult
{
private readonly ApiErrorDetails _details;
public ExceptionAllResult(ApiErrorDetails details)
{
_details = details;
}
public async Task ExecuteAsync(HttpContext httpContext)
{
var jsonDetails = JsonSerializer.Serialize(_details);
httpContext.Response.ContentType = MediaTypeNames.Application.Json;
httpContext.Response.ContentLength = Encoding.UTF8.GetByteCount(jsonDetails);
httpContext.Response.StatusCode = _details.StatusCode;
await httpContext.Response.WriteAsync(jsonDetails);
}
}
Return result in your exception handling middleware in your Program.cs file.
app.UseExceptionHandler(
x =>
{
x.Run(
async context =>
{
// https://learn.microsoft.com/en-us/aspnet/core/fundamentals/error-handling?view=aspnetcore-6.0
var exceptionFeature = context.Features.Get<IExceptionHandlerPathFeature>();
// Whatever you want for null handling
if (exceptionFeature is null) throw new Exception();
// My result service for creating my API details from the HTTP context and exception. This returns the Result class seen in the code snippet above
var result = resultService.GetErrorResponse(exceptionFeature.Error, context);
await result.ExecuteAsync(context); // returns the custom result
});
}
);
If you still want to use MVC (Model-View-Controller), you still can use Custom ActionResult.
If you just want to use Minimal APIs to do the response, then you have to implement IResult, Task<IResult> or ValueTask<IResult>.
app.MapGet("/hello", () => Results.Ok(new { Message = "Hello World" }));
The following example uses the built-in result types to customize the response:
app.MapGet("/api/todoitems/{id}", async (int id, TodoDb db) =>
await db.Todos.FindAsync(id)
is Todo todo
? Results.Ok(todo)
: Results.NotFound())
.Produces<Todo>(StatusCodes.Status200OK)
.Produces(StatusCodes.Status404NotFound);
You can find more IResult implementation samples here: https://github.com/dotnet/aspnetcore/tree/main/src/Http/Http.Results/src
Link: Minimal APIs overview | Microsoft Docs

How to fetch initial data using provider in flutter effectievly

Recently, i did a flutter course.
The instructor was making the get request from an API so difficult. For a hybrid framework like flutter i never thought it's so difficult.
below are my code. I am using provider for state management.
Future<void> fetchAndSetProducts() async {
try {
const url = 'fetch-url';
final response = await http.get(url);
final data = json.decode(response.body) as Map<String, dynamic>;
final List<Product> loadedProducts = [];
data.forEach((key, value) {
loadedProducts.add(Product(
id: key,
title: value['title'],
description: value['description'],
imageUrl: value['imageUrl'],
price: value['price'],
isFavorite: value['isFavorite'],
));
});
_items = loadedProducts;
notifyListeners();
} catch (error) {
throw (error);
}
}
And in the products overview screen were I am showing the products page this method is called like below:
bool _isInit = true;
bool _isLoading = false;
#override
void didChangeDependencies() {
if (_isInit) {
setState(() {
_isLoading = true;
});
Provider.of<Products>(context).fetchAndSetProducts().then((_) => {
setState(() {
_isLoading = false;
})
});
}
_isInit = false;
super.didChangeDependencies();
}
The other method included a sneaky way of using duration of zero just like we use in javascript set timeout giving a zero time.
It's worth noting that in didChangeDependencies we could not use async await, so most probably a call back hell awaits.
Also a variable needs to be initialized just for calling the api once upon loading.
Is there no easy solution to this? Or an industry way of dealing with this?
here is a minimal working example of what you can do, it's not the best thing in the world, but this is what works for me, let me know if you can make it any better.
The answer to your problem is really simple, BUT, you need to rearrange some stuff first.
A Flutter app can be split into multiple layers which are (just for example) data, state management and UI, in the data layer you will have all methods that communicate with the API, and you call them inside the state management solution (which is provider in your case), the result will be accessible from the provider which will save the data in a variable, then the UI will be able to retrieve these data from the provider, this seems a bit redundant I know, but there is a reason why we do that, if you put the API call inside the provider itself, and there is somewhere else in your app that uses the same endpoint then you will have duplicate code, as for the provider, it's the place where your data is stored in the runtime, these data are what makes the state of your app, finally, the UI can handle displaying data from the provider easily, just make a boolean in the provider that indicates if the API call is executing/loading or not, and inside the consumer in the UI display different widgets based on the boolean.
If we were to visualize the flow of the operation it would be like this:
1- action from the UI that triggers a method from the provider.
2- inside the provider method you will set the boolean indicating that the API call is executing to true and call notifyListeners().
3- call the API request and call .then() on it.
4- inside the .then() set the boolean to false to notify that the call is over and set the received data to a variable inside the provider and call notifyListeners again.
5- in the UI you should have a consumer listening to your provider and handling the boolean, if its true then display a CircularProgressIndicator for example, and if it's false then display your desired widget
Regarding the context in the initState you can fix this problem in 3 ways:
1- using WidgetsBinding.instance
.addPostFrameCallback((_) => yourProviderFunction(context));
2- by registering your provider in a service locator so you don't have to use a context at all. (which is what I used in the example project I posted above)
3- by executing the desired function in the constructor of the provider, so when its initialized the API request will be called
Is this the Academind course?
Also this is the correct way.
For using a Provider you need the context.
EDIT: Added BaselAbuhadrous' comment to the answer.
You need to use didChangeDependencies because the initState actually provides the context, but the screen layout isn't built yet, so you get an error, but if you used WidgetsBindings.instance and call the provider inside of it, then you won't get the error.
//your model , product_model.dart
import 'dart:convert';
List<Product> availableTicketsFromJson(String str) => List<Product>.from(json.decode(str).map((x) => Product.fromJson(x)));
class Product {
String title;
String description;
String imageUrl;
double price;
bool isFavorite;
Product(
{this.title,
this.description,
this.imageUrl,
this.price,
this.isFavorite});
factory Product.fromJson(Map<String, dynamic> json) => Product(
title: json['title'] as String,
description: json['description'] as String,
imageUrl: json['imageUrl'] as String,
price: json['price'] as double,
isFavorite: json['isFavorite'] as bool,
);
}
//viewmodel class
final String url = "test.com";
Future<List<Product> fetchProducts() async {
List<Product> products = List<Product>();
try {
final request = await http.get(url);
if(request.statusCode == 200) {
products = productsFromJson(request.body.toString());
notifyListeners();
} else {
print(request.statusCode.toString());
}
} catch(e) {
return List<Product>();
}
return products;
}
//fetch_data.dart
Create your instance of provider in the page that you wanna fetch the data:
Under State<yourWidget>
=> FetchDataViewModel _model;
List<Product> products = [];
under build method
=> _model = Provider.of<FetchDataViewModel>(context,listen: false);
Make a http request with FutureBuilder
FutureBuilder(future:_model.fetchProducts()),
builder: (context,snapshot)){
if(snapshot.connectionState == ConnectionState.done) {
products = snapshot.data;
if(products.length > 0) {
return ListView.builder(
itemCount: products.length,
itemBuilder : (context,index) {
return _items();
}
);
} else {return _noSavedDataWidget();}
}
}
You can test such a code
sometimes
Provider.of<'providerClassName'>(context, listen : false).'providerFunction'
might help.

Fetching data simultaneously in flutter

I need to a clarification on this,I create a function with sharepreferrence which contain asyn, And in this same function am trying to fetch data from the server, And Am using FutureBuilder to display the output
BUT what i observe is that the data keep fetching simultaneously,
What can it be the Problem. Here is the code
My Function for fetching Data
List<TransactionsModel> parseTransactons (String responseBody) {
Map data = json.decode(responseBody);
var output = data['transactions']; //returns a List of Maps
final castingtoMAP = output.cast<Map<String, dynamic>>();
return castingtoMAP.map<TransactionsModel>((json) =>
TransactionsModel.fromJson(json)).toList();
}
Future<List<TransactionsModel>> fetchTransactions(http.Client client) async {
final SharedPreferences prefs = await
SharedPreferences.getInstance();
String email = prefs.getString('Email');
String session = prefs.getString('session');
var map = new Map<String, String>();
map["email"] = email;
map["sesscode"] = session;
var response = await http.post(new API().Transaction, body: map);
print('*********Response from Transaction API***** $response');
return parseTransactons(response.body);
}
Displaying Data
FutureBuilder<List<TransactionsModel>>(
future: fetchTransactions(http.Client()),
builder: (context, snapshot) {
if (snapshot.hasError)
return MyFunctions().showToast(snapshot.error);
return snapshot.hasData
? TransactionCardWidget(snapshot.data)
: Center(child: CircularProgressIndicator());
},
),
If your widget is Stateless or is Stateful and you are calling setState a new instance of Future<List<TransactionsModel>> is being created by calling fetchTransactions, thus making a new request. You need to save the Future that the function returns in the State of a StatefulWidget and then call fetchTransactions in the initState function.
class .... extends State<..> {
Future<List<TransactionsModel>> transactionsFuture;
void initState() {
transactionsFuture = fetchTransactions(http.Client());
}
Widget build(BuildContext context){
...
FutureBuilder<List<TransactionsModel>>(
future: transactionsFuture,
builder: (context, snapshot) {
...
}
}

Flutter: how to mock Bloc

I would like to mock my Bloc in order to test my view.
For example, this is my Bloc:
class SearchBloc extends Bloc<SearchEvent, SearchState> {
#override
// TODO: implement initialState
SearchState get initialState => SearchStateUninitialized();
#override
Stream<SearchState> mapEventToState(SearchState currentState, SearchEvent event) async* {
if (event is UserWrites) {
yield (SearchStateInitialized.success(objects);
}
}
}
And this is the view:
class _SearchViewState extends State<SearchView> {
final TextEditingController _filterController = new TextEditingController();
#override
void initState() {
_filterController.addListener(() {
widget._searchBloc.dispatch(FetchByName(_filterController.text));
}
}
TextField buildAppBarTitle(BuildContext context) {
return new TextField(
key: Key("AppBarTextField"),
controller: _filterController,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: buildAppBarTitle(context),),
body: buildBlocBuilder(),
);
}
BlocBuilder<SearchEvent, SearchState> buildBlocBuilder() {
return BlocBuilder(
bloc: widget._searchBloc,
builder: (context, state) {
if (state is SearchStateUninitialized) {
return Container(
key: Key("EmptyContainer"),
);
}
return buildInitializedView(state, context);
}
});
buildInitializedView(SearchStateInitialized state, BuildContext context) {
if (state.objects.isEmpty) {
return Center(child: Text("Nothing found"),);
} else {
return buildListOfCards();
}
}
}
Now, this is my test:
testWidgets('Should find a card when the user searches for something', (WidgetTester tester) async {
_searchView = new SearchView(_searchBloc);
when(mockService.find( name: "a")).thenAnswer((_) =>
[objects]);
await tester.pumpWidget(generateApp(_searchView));
await tester.enterText(find.byKey(Key("searchBar")), "a");
await tester.pump();
expect(find.byType(Card), findsOneWidget);
});
}
As you can see, I just want to test that, when the user writes something in the search, and the object he's looking for exists, a card should be shown.
If I understood correctly, you are mocking some service that is used by the searchBloc. I personally try to design the app in a way that the app only depends on a bloc and the bloc may depend on some other services. Then when I would like to make a widget test, I only need to mock the bloc. You can use bloc_test package for that.
There is this example on the bloc_test page for stubbing a counterBloc:
// Create a mock instance
final counterBloc = MockCounterBloc();
// Stub the bloc `Stream`
whenListen(counterBloc, Stream.fromIterable([0, 1, 2, 3]));
however, I often do not need to stub the bloc stream and it is enough to emit the state, like this
when(counterBloc.state).thenAnswer((_) => CounterState(456));
Hope this helps.
Have a look at a post from David Anaya which deal with Unit Testing with “Bloc” and mockito.
The last version of his example is here
Sometimes widgets require a little time to build. Try with:
await tester.pumpWidget(generateApp(_searchView));
await tester.enterText(find.byKey(Key("searchBar")), "a");
await tester.pump(Duration(seconds: 1));
expect(find.byType(Card), findsOneWidget);
To mock the bloc, you can use the bloc_test package
Also, you may watch this tutorial which covers bloc testing include mock bloc very nice.

Why is data not being refreshed in StreamBuilder?

I am making a movie list application in Flutter and when I open the app I am displaying most-viewed movies list. When I click a button, I fetch new data and add it to the sink which is then sent to the StreamBuilder and should refresh the screen with new data.
But that doesn't happen. I cannot fathom why!
Here is my code for the Repository:
class MoviesRepository {
final _movieApiProvider = MovieApiProvider();
fetchAllMovies() async => _movieApiProvider.fetchMovieList();
fetchAllSimilarMovies(genreIdeas) async => await _movieApiProvider.fetchMoviesLikeThis(genreIdeas);
fetchTopRatedMovies() async => await _movieApiProvider.fetchTopRatedMovieList();
}
Here is my code for bloc:
class MoviesBloc {
final _moviesRepository = MoviesRepository();
final _moviesFetcher = BehaviorSubject<Result>();
Sink<Result> get allMovies => _fetcherController.sink;
final _fetcherController = StreamController<Result>();
Observable<Result> get newResults => _moviesFetcher.stream;
fetchAllMovies() async {
Result model = await _moviesRepository.fetchAllMovies();
allMovies.add(model);
}
fetchTopRatedMovies() async{
Result model = await _moviesRepository.fetchTopRatedMovies();
allMovies.add(model);
}
dispose() {
_moviesFetcher.close();
_fetcherController.close();
}
MoviesBloc() {
_fetcherController.stream.listen(_handle);
}
void _handle(Result event) {
_moviesFetcher.add(event);
}
}
final bloc = MoviesBloc();
UPDATE
And here is my code for the StreamBuilder:
class HomeScreenListState extends State<HomeScreenList> {
#override
void initState() {
super.initState();
print("init");
bloc.fetchAllMovies();
}
#override
void dispose() {
super.dispose();
bloc.dispose();
}
#override
Widget build(BuildContext context) {
return StreamBuilder<Result>(
builder: (context, snapshot) {
if (snapshot.hasData) {
print("new data is here");
return buildList(snapshot);
} else if (snapshot.hasError) {
return Text('Error!!');
}
return Center(
child: CircularProgressIndicator(),
);
},
stream: bloc.newResults,
);
}
}
And this is the button that triggers to get new data and add it to the sink:
child: RaisedButton(onPressed: () {
bloc.fetchTopRatedMovies();
},
This button fetches top-rated movies and add to the same sink. Now the StreamBuilder should pick up the new data as I think.
Where is the problem??
You can use Equatable alongside your BLoC implementation to manage state changes on your screen. Here's a guide that I suggest trying out.