Where to put Flutter AJAX? - flutter

I come from the web dev world. I have designed a Flutter app that needs to grab some JSON from the web very early on. I want my first screen to show up, and while it is being drawn, behind the scenes I want the JSON fetch to happen. There is a start button on Page 1 that will be disabled until the JSON is fetched. (But Page 1 will have some text info to keep the reader engaged until the fetch happens.)
Where would I stick the JSON fetch? Can I put it in initState of Page 1? Or can I initiate a call at the very start of main at the root of the app? Or somewhere else?
FWIW I use Provider for state management, if that helps?
Thanks a ton!

Calling it in the initState is best since your widgets still build. Since there will be a state change, make sure you use a stateful widget. Also, don't forget to call setState to re-enable the button. Example code:
class FirstPage extends StatefulWidget {
const FirstPage({Key? key}) : super(key: key);
#override
State<FirstPage> createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
#override
void initState() {
super.initState();
jsonFetch();
}
void jsonFetch() {
// your functionality here
// on complete call:
/*
setState(() {
isJsonFetched = true;
});
*/
}
void doSomethingOnPressed(){
// Your functionality here
}
bool isJsonFetched = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: ElevatedButton(
onPressed: (){
isJsonFetched
? doSomethingOnPressed()
:null;
},
child: Text('Press Me')
)
)
);
}
}

Related

Flutter: How to show a loading page while another page is loading

I'm making a website and I would like to show a loading_page until the home_page is loaded and then transition from one to the other as soon as possible (no fixed timers).
There are multiple ways to do this (ie., using the simplest setState, Streams, multiple packages for state management, just to name a few). I'll give you a simple example just by using a StatefulWidget where you call your API on initState and then navigate when you're done to your new screen.
class LoadingPage extends StatefulWidget {
const LoadingPage({Key? key}) : super(key: key);
#override
_LoadingPageState createState() => _LoadingPageState();
}
class _LoadingPageState extends State<LoadingPage> {
#override
void initState() {
super.initState();
_fetchFromAPI();
}
Future<void> _fetchFromAPI() async {
// Call some API, do anything you want while loading
Navigator.pushReplacementNamed(context, '/home_page');
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: const CircularProgressIndicator(),
);
}
}
You can use future builder for this purpose.
Have a look at: https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
You can fetch the data as snapshot and use the snapshot.hasdata to check if data is being received or not and till then you can show CircularProgreswIndicator() to show the loading..

Flutter: How to always fire a function when a page appears which was already visited

Is there a way to always fire a function when visiting a page which was already visited?
I have a page that looks like this:
// Loads of imports
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage>
List<Shift> shifts = [];
Future<void> getUpcomingShifts() async {
var shiftList = await ShiftService.upcomingShifts();
setState(() {
shifts = shiftList;
});
}
#override
void initState() {
super.initState();
this.getUpcomingShifts();
}
#override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
return Scaffold(
extendBody: true,
drawer: SideDrawer(),
body: Column(children: <Widget>[
Header(title: I18n.of(context).pagesHomeTitle),
Container(
child: // The rest of the page
)
]));
}
}
So I want the getUpcomingShifts to always fire when this (home) page is visited. The first time the page is visited this works fine, but after initialization the function never fires again. I can't find any good examples or docs on this matter.
You can use the RouteAware mixin on state and then call your function in the didPopNext which gets called when the next route pops off the stack and this page becomes visible again

Flutter PageView and FutureBuild Keeps loading from api every time i access the page

i am facing an issue with Pageview and Futurebuild, that every-time i switch between first page and second page the page will be rebuilt again...it will call new data from API even if there is nothing new...anyone can help me in this
class Home extends StatefulWidget {
const Home({Key key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
PageController pageController = PageController(keepPage: false);
#override
void initState() {
super.initState();
}
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
final homePage = HomeProjects(pageController: pageController);
final portfolioPage = Portfolio(pageController: pageController);
return Scaffold(
body: ScrollConfiguration(
behavior: MyBehavior(),
child: PageView(reverse: true, controller: pageController,
//physics: NeverScrollableScrollPhysics(),
children: [
homePage,
portfolioPage,
]),
),
);
}
}
If you are only making HTTP calls inside your pages that's a desired outcome. You'll need to cache the responses to avoid repeated network calls.
Moving api calls to initState is not something I would personally do - the data fetched on first page creation may become outdated without you knowing it.
Well, you build your pages every time build is called.
If you don't want that, move the lines that actually build the pages to a time and place where they will be called only once. initState might be a good place.

How to reload the page whenever the page is on screen - flutter

Is there any callbacks available in flutter for every time the page is visible on screen? in ios there are some delegate methods like viewWillAppear, viewDidAppear, viewDidload.
I would like to call a API call whenever the particular page is on-screen.
Note: I am not asking the app states like foreground, backround, pause, resume.
Thank You!
Specifically to your question:
Use initState but note that you cannot use async call in initState because it calls before initializing the widget as the name means. If you want to do something after UI is created didChangeDependencies is great. But never use build() without using FutureBuilder or StreamBuilder
Simple example to demostrate:
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(MaterialApp(home: ExampleScreen()));
}
class ExampleScreen extends StatefulWidget {
ExampleScreen({Key key}) : super(key: key);
#override
_ExampleScreenState createState() => _ExampleScreenState();
}
class _ExampleScreenState extends State<ExampleScreen> {
List data = [];
bool isLoading = true;
void fetchData() async {
final res = await http.get("https://jsonplaceholder.typicode.com/users");
data = json.decode(res.body);
setState(() => isLoading = false);
}
// this method invokes only when new route push to navigator
#override
void initState() {
super.initState();
fetchData();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: isLoading
? CircularProgressIndicator()
: Text(data?.toString() ?? ""),
),
);
}
}
Some lifecycle method of StatefulWidget's State class:
initState():
Describes the part of the user interface represented by this widget.
The framework calls this method in a number of different situations:
After calling initState.
After calling didUpdateWidget.
After receiving a call to setState.
After a dependency of this State object changes (e.g., an InheritedWidget referenced by the previous build changes).
After calling deactivate and then reinserting the State object into the tree at another location.
The framework replaces the subtree below this widget with the widget
returned by this method, either by updating the existing subtree or by
removing the subtree and inflating a new subtree, depending on whether
the widget returned by this method can update the root of the existing
subtree, as determined by calling Widget.canUpdate.
Read more
didChangeDependencies():
Called when a dependency of this State object changes.
For example, if the previous call to build referenced an
InheritedWidget that later changed, the framework would call this
method to notify this object about the change.
This method is also called immediately after initState. It is safe to
call BuildContext.dependOnInheritedWidgetOfExactType from this method.
Read more
build() (Stateless Widget)
Describes the part of the user interface represented by this widget.
The framework calls this method when this widget is inserted into the
tree in a given BuildContext and when the dependencies of this widget
change (e.g., an InheritedWidget referenced by this widget changes).
Read more
didUpdateWidget(Widget oldWidget):
Called whenever the widget configuration changes.
If the parent widget rebuilds and request that this location in the
tree update to display a new widget with the same runtimeType and
Widget.key, the framework will update the widget property of this
State object to refer to the new widget and then call this method with
the previous widget as an argument.
Read more
Some widgets are stateless and some are stateful. If it's a stateless widget, then only values can change but UI changes won't render.
Same way for the stateful widget, it will change for both as value as well as UI.
Now, will look into methods.
initState(): This is the first method called when the widget is created but after constructor call.
#override
void initState() {
// TODO: implement initState
super.initState();
}
didChangeDependecies() - Called when a dependency of this State object changes.Gets called immediately after initState method.
#override
void didChangeDependencies() {
super.didChangeDependencies();
}
didUpdateWidget() - It gets called whenever widget configurations gets changed. Framework always calls build after didUpdateWidget
#override
void didUpdateWidget (
covariant Scaffold oldWidget
)
setState() - Whenever internal state of State object wants to change, need to call it inside setState method.
setState(() {});
dispose() - Called when this object is removed from the tree permanently.
#override
void dispose() {
// TODO: implement dispose
super.dispose();
}
You don't need StatefulWidget for calling the api everytime the screen is shown.
In the following example code, press the floating action button to navigate to api calling screen, go back using back arrow, press the floating action button again to navigate to api page.
Everytime you visit this page api will be called automatically.
import 'dart:async';
import 'package:flutter/material.dart';
main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => ApiCaller())),
),
);
}
}
class ApiCaller extends StatelessWidget {
static int counter = 0;
Future<String> apiCallLogic() async {
print("Api Called ${++counter} time(s)");
await Future.delayed(Duration(seconds: 2));
return Future.value("Hello World");
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Api Call Count: $counter'),
),
body: FutureBuilder(
future: apiCallLogic(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) return const CircularProgressIndicator();
if (snapshot.hasData)
return Text('${snapshot.data}');
else
return const Text('Some error happened');
},
),
);
}
}
This is the simple code with zero boiler-plate.
The simplest way is to use need_resume
1.Add this to your package's pubspec.yaml file:
dependencies:
need_resume: ^1.0.4
2.create your state class for the stateful widget using type ResumableState instead of State
class HomeScreen extends StatefulWidget {
#override
HomeScreenState createState() => HomeScreenState();
}
class HomeScreenState extends ResumableState<HomeScreen> {
#override
void onReady() {
// Implement your code inside here
print('HomeScreen is ready!');
}
#override
void onResume() {
// Implement your code inside here
print('HomeScreen is resumed!');
}
#override
void onPause() {
// Implement your code inside here
print('HomeScreen is paused!');
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text('Go to Another Screen'),
onPressed: () {
print("hi");
},
),
),
);
}
}
If you want to make an API call, then you must be (or really should be) using a StatefulWidget.
Walk through it, let's say your stateful widget receives some id that it needs to make an API call.
Every time your widget receives a new id (including the first time) then you need to make a new API call with that id.
So use didUpdateWidget to check to see if the id changed and, if it did (like it does when the widget appears because the old id will be null) then make a new API call (set the appropriate loading and error states, too!)
class MyWidget extends StatefulWidget {
Suggestions({Key key, this.someId}) : super(key: key);
String someId
#override
State<StatefulWidget> createState() => MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
dynamic data;
Error err;
bool loading;
#override
Widget build(BuildContext context) {
if(loading) return Loader();
if(err) return SomeErrorMessage(err);
return SomeOtherStateLessWidget(data);
}
#override
void didUpdateWidget(covariant MyWidget oldWidget) {
super.didUpdateWidget(oldWidget);
// id changed in the widget, I need to make a new API call
if(oldWidget.id != widget.id) update();
}
update() async {
// set loading and reset error
setState(() => {
loading = true,
err = null
});
try {
// make the call
someData = await apiCall(widget.id);
// set the state
setState(() => data = someData)
} catch(e) {
// oops an error happened
setState(() => err = e)
}
// now we're not loading anymore
setState(() => loading = false);
}
}
I'm brand new to Flutter (literally, just started playing with it this weekend), but it essentially duplicates React paradigms, if that helps you at all.
Personal preference, I vastly prefer this method rather than use FutureBuilder (right now, like I said, I'm brand new). The logic is just easier to reason about (for me).

Flutter - animation widget, trigger animation only once but get data upates on widget

I came across the following problem already twice within my app:
Inside a StatefulWidget I have a widget that contains an animation and additionally displays other dynamic data. The animation is triggered after the animation widget has been built. However, after the animation (the drawing of a figure) has been played, I want to maintain it in the same drawn state. The rest of the dynamic data of the widget should be always rebuilt when the parent StatefulWidget's build method is called.
Below, there is a simple example of what I try to achieve:
class MyStatefulWidget extends StatefulWidget {
MyStatefulWidget({Key key, #required this.data}) : super(key: key);
CustomData data;
#override
State<StatefulWidget> createState() => _MyState();
}
Now, I had two ideas of how to implement the state:
1) Initialize the animation widget in initState() and "manually" handle data updates within didUpdateWidget().
class _MyState extends State<MyStatefulWidget> {
AnimationWidget _animationWidget;
#override
void initState() {
super.initState();
_animationWidget = AnimationWidget(displayData: widget.data);
// starts the animation when the widget is built (?)
WidgetsBinding.instance
.addPostFrameCallback((_) => _animationWidget.startAnimation());
}
#override
Widget build(BuildContext context) {
return Column(
children: [
_animationWidget,
... // other content
],
... // column size etc.
);
}
#override
void didUpdateWidget(SackenNotebookContent oldWidget) {
super.didUpdateWidget(oldWidget);
if(dataChanged(oldWidget.data, widget.data))
setState(() {
_animationWidget = AnimationWidget(displayData: widget.data);
});
}
}
2) To create the animation widget within the build() method. But then I already have doubts about when/where to trigger the animation to start. One way would be to trigger it in didUpdateWidget(). But again, the animation would then be triggered on every rebuild of the widget...
class _MyState extends State<MyStatefulWidget> {
#override
Widget build(BuildContext context) {
var animationWidget = AnimationWidget(displayData: widget.data);
// but where to call animationWidget.startAnimation() ?!
// when I call it here I get a null pointer exception...
return Column(
children: [
animationWidget,
... // other content
],
... // column size etc.
);
}
}
Has anyone got an idea of how I could handle such a problem in flutter?