How to show loading spinner in GetBuilder - flutter

In FutureBuilder when working with an API you can easily show loading spinner when data is not yet available with this code,
if(snapshot.connectionState == ConnectionState.waiting){
return Center(
child: CircularProgressIndicator(),
);
}
how do I do same for GetBuilder when using getx as state management library?

Here's a basic example of re-building based on the value of an isLoading bool. I'm just changing the value of a String but this should give you the idea of doing a proper API call in a GetX function and displaying an indicator. While I typically default to using GetBuilder whenever possible, showing loading indicators I generally just use Obx so I don't have to call update() twice.
class TestController extends GetxController {
bool isLoading = false;
String data = '';
Future<void> fetchData() async {
isLoading = true;
update(); // triggers the GetBuilder rebuild
await Future.delayed(
const Duration(seconds: 2),
() => data = 'Data Loaded',
);
isLoading = false;
update();
}
}
You can test this by throwing this in a Column. Just make sure the controller is initialized first at some point with Get.put(TestController());
GetBuilder<TestController>(
builder: (controller) => controller.isLoading
? CircularProgressIndicator()
: Text(controller.data)),
ElevatedButton(
onPressed: () => controller.fetchData(),
child: Text('Fetch Data'),
),
If you don't want to have to manually call the function you can also lose the isLoading bool use a FutureBuilder but then just pass a Future function from a GetX class to keep that logic out of your UI.
Update
Here's an example using live dummy data of random Kanye quotes from
https://api.kanye.rest Copy the code below into your IDE and run it and it should make sense.
Basic ApiCaller class
class ApiCaller extends GetConnect {
final url = 'https://api.kanye.rest';
Future<String> fetchData() async {
final response = await httpClient.get(url);
return response.body['quote'] as String;
}
}
Updated TestController class
class TestController extends GetxController {
String data = 'no data';
bool isLoading = false;
Future<void> updateData() async {
_updateIsLoading(true);
final apiCaller = ApiCaller();
await Future.delayed(
const Duration(seconds: 1),
() => data = 'Data Loaded',
); // just to add more visible time with loading indicator
data = await apiCaller.fetchData();
_updateIsLoading(false);
}
void _updateIsLoading(bool currentStatus) {
isLoading = currentStatus;
update();
}
}
Example with GetBuilder and FutureBuilder
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
final controller = Get.put(TestController());
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
FutureBuilder(
future: ApiCaller().fetchData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text('FutureBuilder: ${snapshot.data}');
} else {
return CircularProgressIndicator();
}
},
),
GetBuilder<TestController>(
builder: (_) => controller.isLoading
? CircularProgressIndicator()
: Text('GetBuilder: ${controller.data}'),
),
ElevatedButton(
onPressed: () => controller.updateData(),
child: Text('Update GetBuilder'),
),
],
),
),
),
);
}
}
Example with FutureBuilder with function from GetX class

Related

How do I move the function **Future<void> fetchData()** to a service class

I'd like to restructure my code to MVVM, I'm new to flutter, How do I restructure the function above to a Service Class
import 'dart:convert';
import 'package:amaizi_test/models/UserModel.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class JsonApiPhp extends StatefulWidget {
#override
_JsonApiPhpState createState() => _JsonApiPhpState();
}
class _JsonApiPhpState extends State<JsonApiPhp> {
bool loading = true;
final String url = 'https://jsonplaceholder.typicode.com/users';
var client = http.Client();
List<UserModel> users = [];
#override
void initState(){
fetchData();
super.initState();
}
I'd like to restructure my files,
Future<void> fetchData() async {
http.Response response = await client.get(Uri.parse(url));
if(response.statusCode == 200){ // Connection Ok
List responseJson = json.decode(response.body);
responseJson.map((m) => users.add(new UserModel.fromJson(m))).toList();
setState(() {
loading = false;
});
} else {
throw('error');
}
}
and inititialize in HomePage InitState
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
child: loading ?
Container(
child: Center(
child: CircularProgressIndicator(),
),
) :
ListView.builder(
itemCount: users.length,
itemBuilder: (BuildContext context, int index){
return Card(
child: ListTile(
title: Text(
users[index].name,
),
),
);
},
)
),
);
}
}
// The function calls API and displays in UI
For example you can create directory "services" inside lib dir, then create inside that directory dart file with your service class.
For example:
class ServerApiService{
Future<List<User>> fetchData() async {
///your code, but without setState()
return users;
}
}
After it in initState you can create service object and call your fetchData function from service class. But instead of create service class object every time, I recommend to create service object above MaterialApp and use Provider for access service object in any widget of app.
You can use FutureBuilder widget for access data from fetchData() method.
FutureBuilder<List<User>>(
future: users,
builder: (context, snapshot) {
if (snapshot.hasData) {
var data = snapshot.data!;
return ListView.builder(
itemCount: data.length,
itemBuilder: (BuildContext context, int index) {
User user = data[index];
return Card(
child: ListTile(
title: Text(
user.name,
),
),
)
});
}
else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
else {
return Container(
child: Center(
child: CircularProgressIndicator(),
),
);
}
});
Where users which access FutureBuilder (future: users) is fetchedData from service object.
And also little note: better if inside initState() you will call super.initState() above other code.
Start your initState with super.initState and end your dispose with super.dispose

Flutter Cubit Web

For retrieve items from FireStore and for pick image i am using cubit.
Cubit:
class ItemCubit extends Cubit<ItemState> {
ItemCubit(this._dataBase)
: super(ItemInitial());
final DataBase _dataBase;
StreamSubscription streamSubscription;
Future<void> pickItemImg() async {
final currentTempImg =
await ImagePickerWeb.getImage(outputType: ImageType.bytes);
emit(ItemImgPicked(currentTempImg));
}
Future getItem() async {
streamSubscription = _dataBase.getItem().listen((data) {
emit(ItemLoaded(data));
});
}
}
State:
#immutable
abstract class ItemState {}
class ItemLoaded extends ItemState {
final List<Item> item;
ItemLoaded(this.item);
}
class ItemImgPicked extends ItemState {
final Uint8List currentTempImg;
ItemImgPicked(this.currentTempImg);
}
Page with blocbuilders
class Page extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
RaisedButton(
onPressed: () async {
showDialog(
context: context,
builder: (BuildContext context) => Dialog(
child: Container(
width: 400,
child: OutlineButton(
onPressed: () async {
context.bloc<ItemCubit>().pickProductImg();
},
child: BlocBuilder<ItemCubit, ItemState>(
builder: (context, state) {
if (state is ItemImgPicked) {
return Image.memory(state.currentTempImg);
} else {
return Container();
}
},
),
),
),
),
);
},
child: Text('add'),
),
BlocBuilder<ItemCubit, ItemState>(
builder: (context, state) {
if (state is ItemLoaded) {
return Column(
children: state.item.map(
(item) {
return Text(item.name);
},
).toList(),
);
}
return CircularProgressIndicator();
},
)
],
),
);
}
}
Issue is when on show dialog I picked image, the picked image is displayed, but at the same time on main page blocbuilder for item list return CircularProgressIndicator. if I use hot reload at this time, after it shows me the list of item. It looks like the state for picked image replace state for item list. How to solve it?
Your main page bloc builder listens for ItemLoaded which you never emit as far as I can tell. You can put a breakpoint into that line, it should not get hit.
That said, please treat your async functions better, you missed to await some futures, that might not be your problem now, but it will become a problem sooner or later.

How to fix recursing HTTP request in FutureBuilder?

I'm creating an app that has a list inside a screen. What I want to do is whenever the app makes the HTTP request (getting the data), I want to show CircularProgressIndicator() on the screen. I tried to use a FutureBuilder to implement this, but the app recursively/continuously loading the data (when the ListView is set, the app load the data again and again). Here are some of my code:
FutureBuilder Widget
Widget _buildFuture(BuildContext context){
return FutureBuilder(
future: listenForBeers(),
builder: (context, snapshot) {
if(snapshot.connectionState == ConnectionState.done){
if(snapshot.hasError){
print('_buildFuture: Loading error');
return Center(
child: Text(
snapshot.error.toString(),
textAlign: TextAlign.center,
textScaleFactor: 1.3,
),
);
}
print('_buildFuture: Showing the Data');
return _buildBeers();
}
else{
print('_buildFuture: Loading the data');
return Center(
child: Column(
children: <Widget>[
SizedBox(height: 100),
CircularProgressIndicator()
],
),
);
}
}
);
}
initState() and listenForBeers() method
#override
void initState(){
super.initState();
listenForBeers();
}
Future listenForBeers() async {
final Stream<Beer> stream = await getBeers();
stream.listen((Beer beer) => setState(() => _beers.add(beer)));
}
getBeers() method
Future<Stream<Beer>> getBeers() async {
final String url = 'https://api.punkapi.com/v2/beers';
final client = new http.Client();
final streamedRest = await client.send(http.Request('get', Uri.parse(url)));
return streamedRest.stream
.transform(utf8.decoder)
.transform(json.decoder)
.expand((data) => (data as List))
.map((data) => Beer.fromJSON(data));
}
I'm not sure how to implement the right way because I'm new to Flutter as well. If you need other code feel free to ask, and any help would be appreciated. Thanks!
CReate AsyncMemoizer in State Class
AsyncMemoizer _memoizer = AsyncMemoizer();
Now Change
Future listenForBeers() async {
return this._memoizer.runOnce(() async {
final Stream<Beer> stream = await getBeers();
stream.listen((Beer beer) => setState(() => _beers.add(beer)));
)};
}
Future refreshBeers() async {
_memoizer = AsyncMemoizer();
return listenForBeers();
}
Details at https://medium.com/saugo360/flutter-my-futurebuilder-keeps-firing-6e774830bc2
Initialize stream in initstate and keep referance like this.
Stream<Beer> stream;
#override
void initState(){
super.initState();
stream = await getBeers();
stream.listen((Beer beer) => setState(() => _beers.add(beer)));
}

Flutter : Prevent FutureBuilder always refresh every change screen

I have FutureBuilder to fetch User profil from API and code to fetch user like this :
Future<List<UserModel>> getUserByUsername({#required String username}) async {
try {
final response =
await _client.get("$_baseUrl/getUserByUsername?username=$username");
final Map<String, dynamic> responseJson = json.decode(response.body);
if (responseJson["status"] == "ok") {
List userList = responseJson['data'];
final result = userList
.map<UserModel>((json) => UserModel.fromJson(json))
.toList();
return result;
} else {
throw CustomError(responseJson['message']);
}
} catch (e) {
return Future.error(e.toString());
}
}
If you can see in above GIF, My FutureBuilder are inside BottomNavigationBar. Every i change the screen/page from BottomNavigationBar and come back to my FutureBuilder is always refresh !
How can i fixed it to only once to refresh ?
Home Screen
class _HomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
final username = Provider.of<SharedPreferencesFunction>(context).username;
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
CardTime(),
FutureBuilder(
future: userApi.getUserByUsername(username: username),
builder: (BuildContext context,
AsyncSnapshot<List<UserModel>> snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return Center(
child: Text(
snapshot.error.toString(),
),
);
} else {
final user = snapshot.data[0];
return CardProfil(
imageUrl: "${userApi.baseImageUrl}/${user.fotoUser}",
detailProfil: [
Text(
user.namaUser,
style: TextStyle(fontWeight: FontWeight.bold),
),
Text(user.idDevice),
],
);
}
} else {
return Center(
child: CircularProgressIndicator(),
);
}
},
),
],
),
);
}
}
Shared Preferences Function
import 'package:flutter/cupertino.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SharedPreferencesFunction extends ChangeNotifier {
SharedPreferencesFunction() {
initialSharedPreferences();
getUsername();
}
String _username;
String get username => _username;
void initialSharedPreferences() {
getUsername();
}
Future updateUsername(String username) async {
SharedPreferences pref = await SharedPreferences.getInstance();
await pref.setString("username", username);
//! It's Important !!! After update / remove sharedpreferences , must called getUsername() to updated the value.
getUsername();
notifyListeners();
}
Future removeUsername() async {
SharedPreferences pref = await SharedPreferences.getInstance();
final result = await pref.remove("username");
//! It's Important !!! After update / remove sharedpreferences , must called getUsername() to updated the value.
getUsername();
print(result);
notifyListeners();
}
Future getUsername() async {
SharedPreferences pref = await SharedPreferences.getInstance();
final result = pref.getString("username");
_username = result;
notifyListeners();
}
}
final sharedpref = SharedPreferencesFunction();
Update Question
I already try Initialize FutureBuilder and use initState and didChangeDependencies . But new problem is , if i initialize inside initState my profil not rebuild because Provider listen=false.
If i using didChangeDependencies my FutureBuilder still refresh every i change screen.
Something wrong ?
Using initState
Using didChangeDependencies
Initialize the Future during initState or didChangeDependencies instead.
class _HomeScreenState extends State<HomeScreen> {
Future<List<UserModel>> user;
#override
void initState() {
super.initState();
// must use listen false here
final username = Provider.of<SharedPreferencesFunction>(context, listen: false).username;
user = userApi.getUserByUsername(username: username);
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
final username = Provider.of<SharedPreferencesFunction>(context).username;
user = userApi.getUserByUsername(username: username);
}
#override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
FutureBuilder(
future: user,
builder: (context, snapshot) {
// ...
},
),
],
),
);
}
}
I faced a similar case and use AutomaticKeepAliveClientMixin on each view / page / tab bar view / widget / child to keep the page not refreshing every time I go back and forth through the tab bar.
class YourClass extends StatefulWidget {
YourClass({
Key key
}): super(key key);
#override
_YourClassState createState() => _YourClassState();
}
// Must include AutomaticKeepAliveClientMixin
class _YourClassState extends State<YourClass> with AutomaticKeepAliveClientMixin {
Future resultGetData;
void getData() {
setState(() {
resultGetData = getDataFromAPI();
});
}
// Must include
#override
bool get wantKeepAlive => true;
#override
void initState() {
getData();
super.initState();
}
#override
Widget build(BuildContext context) {
super.build(context); // Must include
return FutureBuilder(
future: resultGetAllByUserIdMCId,
builder: (context, snapshot) {
// ...
// Some Code
// ...
}
);
}
}
If you want to refresh the data you could use RefreshIndicator that runs the getData() function. Put this code inside FutureBuilder. The key: PageStorageKey(widget.key) will keep the scroll in the exact same place where you left of.
return RefreshIndicator(
onRefresh: () async {
getData();
},
child: ListView.separated(
key: PageStorageKey(widget.key),
itemCount: data.length,
separatorBuilder: (BuildContext context, int index) {
return Divider(height: 0);
},
itemBuilder: (context, index) {
return ...;
},
),
);
Use IndexedStack as the parent of tabbar.
You have to put your Future Builder in a Stateful Widget then define a
late final Future myFuture;
then you have to initialize it in the initstate so the future will be executed only one time.

pull down to REFRESH in Flutter

My dashboard code looks like this,
Here I am doing get req in getReport method, I have added the RefreshIndicator in the code which when pulled down inside container should do the refresh, there I am calling my getData(), But I am not getting the refreshed content, I am adding my code below, let me know if anywhere I made a mistake.
below my dashboard.dart
class Window extends StatefulWidget {
#override
_WindowState createState() => _WindowState();
}
class _WindowState extends State<Window> {
Future reportList;
#override
void initState() {
super.initState();
reportList = getReport();
}
Future<void> getReport() async {
http.Response response =
await http.get(reportsListURL, headers: {"token": "$token"});
switch (response.statusCode) {
case 200:
String reportList = response.body;
var collection = json.decode(reportList);
return collection;
case 403:
break;
case 401:
return null;
default:
return 1;
}
}
getRefreshScaffold() {
return Center(
child: RaisedButton(
onPressed: () {
setState(() {
reportList = getReport();
});
},
child: Text('Refresh, Network issues.'),
),
);
}
getDashBody(var data) {
double maxHeight = MediaQuery.of(context).size.height;
return Column(
children: <Widget>[
Container(
height: maxHeight - 800,
),
Container(
margin: new EdgeInsets.all(0.0),
height: maxHeight - 188,
child: new Center(
child: new RefreshIndicator( //here I am adding the RefreshIndicator
onRefresh:getReport, //and calling the getReport() which hits the get api
child: createList(context, data),
),),
),
],
);
}
Widget createList(BuildContext context, var data) {
Widget _listView = ListView.builder(
itemCount: data.length,
itemBuilder: (context, count) {
return createData(context, count, data);
},
);
return _listView;
}
createData(BuildContext context, int count, var data) {
var metrics = data["statistic_cards"].map<Widget>((cardInfo) {
var cardColor = getColorFromHexString(cardInfo["color"]);
if (cardInfo["progress_bar"] != null && cardInfo["progress_bar"]) {
return buildRadialProgressBar(
context: context,
progressPercent: cardInfo["percentage"],
color: cardColor,
count: cardInfo["value"],
title: cardInfo["title"],
);
} else {
return buildSubscriberTile(context, cardInfo, cardColor);
}
}).toList();
var rowMetrics = new List<Widget>();
for (int i = 0; i < metrics.length; i += 2) {
if (i + 2 < metrics.length)
rowMetrics.add(Row(children: metrics.sublist(i, i + 2)));
else
rowMetrics.add(Row(children: [metrics[metrics.length - 1], Spacer()]));
}
return SingleChildScrollView(
child: LimitedBox(
// maxHeight: MediaQuery.of(context).size.height / 1.30,
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: rowMetrics,
),
),
);
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: reportList,
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
return Center(
child: CircularProgressIndicator(),
);
case ConnectionState.done:
var data = snapshot.data;
if (snapshot.hasData && !snapshot.hasError) {
return getDashBody(data);
} else if (data == null) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text("Timeout! Log back in to continue"),
Padding(
padding: EdgeInsets.all(25.0),
),
RaisedButton(
onPressed: () {
setState(() {
token = null;
});
Navigator.of(context).pushReplacement(
CupertinoPageRoute(
builder: (BuildContext context) => LoginPage()),
);
},
child: Text('Login Again!'),
),
],
),
);
} else {
getRefreshScaffold();
}
}
},
);
}
}
Basic Example
Below is a State class of a StatefulWidget, where:
a ListView is wrapped in a RefreshIndicator
numbersList state variable is its data source
onRefresh calls _pullRefresh function to update data & ListView
_pullRefresh is an async function, returning nothing (a Future<void>)
when _pullRefresh's long running data request completes, numbersList member/state variable is updated in a setState() call to rebuild ListView to display new data
import 'package:flutter/material.dart';
import 'dart:math';
class PullRefreshPage extends StatefulWidget {
const PullRefreshPage();
#override
State<PullRefreshPage> createState() => _PullRefreshPageState();
}
class _PullRefreshPageState extends State<PullRefreshPage> {
List<String> numbersList = NumberGenerator().numbers;
#override
Widget build(BuildContext context) {
return Scaffold(
body: RefreshIndicator(
onRefresh: _pullRefresh,
child: ListView.builder(
itemCount: numbersList.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(numbersList[index]),
);
},),
),
);
}
Future<void> _pullRefresh() async {
List<String> freshNumbers = await NumberGenerator().slowNumbers();
setState(() {
numbersList = freshNumbers;
});
// why use freshNumbers var? https://stackoverflow.com/a/52992836/2301224
}
}
class NumberGenerator {
Future<List<String>> slowNumbers() async {
return Future.delayed(const Duration(milliseconds: 1000), () => numbers,);
}
List<String> get numbers => List.generate(5, (index) => number);
String get number => Random().nextInt(99999).toString();
}
Notes
If your async onRefresh function completes very quickly, you may want to add an await Future.delayed(Duration(seconds: 2)); after it, just so the UX is more pleasant.
This gives time for the user to complete a swipe / pull down gesture & for the refresh indicator to render / animate / spin indicating data has been fetched.
FutureBuilder Example
Here's another version of the above State<PullRefreshPage> class using a FutureBuilder, which is common when fetching data from a Database or HTTP source:
class _PullRefreshPageState extends State<PullRefreshPage> {
late Future<List<String>> futureNumbersList;
#override
void initState() {
super.initState();
futureNumbersList = NumberGenerator().slowNumbers();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<List<String>>(
future: futureNumbersList,
builder: (context, snapshot) {
return RefreshIndicator(
child: _listView(snapshot),
onRefresh: _pullRefresh,
);
},
),
);
}
Widget _listView(AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(snapshot.data[index]),
);
},);
}
else {
return Center(
child: Text('Loading data...'),
);
}
}
Future<void> _pullRefresh() async {
List<String> freshNumbers = await NumberGenerator().slowNumbers();
setState(() {
futureNumbersList = Future.value(freshNumbers);
});
}
}
Notes
slowNumbers() function is the same as in the Basic Example above, but the data is wrapped in a Future.value() since FutureBuilder expects a Future, but setState() should not await async data
according to RĂ©mi, Collin & other Dart/Flutter demigods it's good practice to update Stateful Widget member variables inside setState() (futureNumbersList in FutureBuilder example & numbersList in Basic example), after its long running async data fetch functions have completed.
see https://stackoverflow.com/a/52992836/2301224
if you try to make setState async, you'll get an exception
updating member variables outside of setState and having an empty setState closure, may result in hand-slapping / code analysis warnings in the future
Not sure about futures, but for refresh indicator you must return a void so
Use something like
RefreshIndicator(
onRefresh: () async {
await getData().then((lA) {
if (lA is Future) {
setState(() {
reportList = lA;
});
return;
} else {
setState(() {
//error
});
return;
}
});
return;
},
Try this and let me know!
EDIT:
Well, then just try this inside you refresh method
setState(() {
reportList = getReport();
});
return reportList;
Try this:
onRefresh: () {
setState(() {});
}}
instead of onRefresh:getReport
reportList field is Future which returns its value once. So, when you call getReport again it changes nothing. Actually, more correctly it'll be with Stream and StreamBuilder instead of Future and FutureBuilder. But for this code it can be shortest solution
Easy method: you can just use Pull Down to Refresh Package - https://pub.dev/packages/pull_to_refresh
In Non-scrollable list view, RefreshIndicator does not work, so you have to wrap your widget with Stack for implementing pull down to refresh.
RefreshIndicator(
onRefresh: () {
// Refresh Functionality
},
child: Stack(
children: [
ListView(
padding: EdgeInsets.zero,
shrinkWrap: true,
children: [
SizedBox(
height: MediaQuery.of(context).size.height,
)
],
),
// Your Widget
],
);
),
I am working on a huge project which contains CustomScrollView, NestedScrollView, ListView, etc I tried every answer above and all of the answers use RefreshIndicator from flutter SDK. It doesn't work entirely with my app because I also have horizontal scroll views. So in order to implement it I had to use NestedScrollView on almost every screen. Then I came to know about liquid_pull_to_refresh, applied it to the top widget, and WOLAAH! If you need a separate logic for each screen then use it at the top of each screen but in my case, I'm refreshing the whole project's data.