Pull to refresh in Flutter doesn't invoke onLoading function unless I switch page and come back - flutter

I am using a smart refresher for pagination in Flutter. It calls the below function to get data from the API on the basis of start and end date.
Whenever the smart refresher calls this function, it gives the right data the first time (with initial start and end date), but when I change the dates, it fetches new data accordingly but now when I pull it again to fetch more data, it doesn't even recognise that pull action as a valid action and nothing happens.
Unless I switch to another screen and come back, it works as expected.
If you need more info or code segments, I'll gladly provide them.
// THIS IS THE FUNCTION CALLED IN SMART REFRESHER in init() isFirst = true otherwise its false
Future<void> fetchAnalytics({required bool isFirst}) async {
if (isFirst == true) {
analyticsLoadingFirst(true);
analyticsPage.value = 1;
analyticsTotalPage.value = 0;
analyticsList.clear();
} else {
analyticsLoading(true);
}
analytics.value = await RemoteServices.getStatsAnalytics(
token: Constants.token!,
start: DateFormat('yyyy-MM-dd'). format(analyticsStartDate.value),
end: DateFormat('yyyy-MM-dd'). format(analytiscEndDate.value),
page: analyticsPage.value,
);
if (analytics.value.data!.data!.isNotEmpty) {
analyticsList.value = analyticsList.value + analytics.value.data!.data!;
analyticsPage.value += 1;
analyticsTotalPage.value = analytics.value.data!.lastPage!;
}
isFirst == true ? analyticsLoadingFirst(false) : analyticsLoading(false);
}
// THIS IS THE TAB
class AnalyticsStatsScreen extends StatefulWidget {
const AnalyticsStatsScreen({Key? key}) : super(key: key);
#override
State<AnalyticsStatsScreen> createState() => _AnalyticsStatsScreenState();
}
class _AnalyticsStatsScreenState extends State<AnalyticsStatsScreen> with TickerProviderStateMixin {
final statsController = Get.find<StatsController>();
late AnimationController animationController;
late AnimationController animationController2;
RefreshController paginateController = RefreshController();
ScrollController? _scroll;
#override
void initState() {
super.initState();
_scroll = ScrollController();
animationController = AnimationController(vsync: this);
animationController2 = AnimationController(vsync: this);
}
#override
void setState(VoidCallback fn) {
if (mounted) super.setState(fn);
}
#override
void dispose() {
animationController.dispose();
animationController2.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Obx(
() => statsController.analyticsLoadingFirst.isTrue
? Center(
child: AlertWidgets.IULoading(animationController1: animationController, animationController2: animationController2),
)
: statsController.analytics.value.data == null
? LoadingError().notFoundWidget()
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 12.h),
/// ****** Today Calendar Button ******
GestureDetector(
onTap: () {
Get.defaultDialog(
title: "Select a range",
titlePadding: EdgeInsets.only(top: 16.h),
titleStyle: TextStyle(fontSize: 18.sp),
contentPadding: EdgeInsets.zero,
content: SizedBox(
// width: 0.5.sw,
// height: 0.65.sw,
width: 0.75.sw,
height: 0.75.sw,
child: SfDateRangePicker(
onSelectionChanged: _onChange,
onSubmit: (value) {
Get.back();
statsController.analyticsPage.value = 1;
statsController.analyticsTotalPage.value = 0;
_scrollUp();
statsController.fetchAnalytics(isFirst: true);
},
onCancel: () {
Get.back();
},
headerStyle: DateRangePickerHeaderStyle(
textStyle: ThemeStyles.NormalLight,
),
selectionMode: DateRangePickerSelectionMode.range,
maxDate: DateTime.now(),
showActionButtons: true,
headerHeight: 50.h,
rangeTextStyle: ThemeStyles.NormalLight,
selectionTextStyle: ThemeStyles.NormalLight.copyWith(color: Constants.TextWhite),
initialSelectedRange: PickerDateRange(statsController.startDate.value, statsController.endDate.value
// DateTime.now(),
// DateTime.now()
// .subtract(Duration(days: 7)),
),
),
),
);
},
child: Container(
width: Get.width,
height: 0.06.sh,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4.r),
border: Border.all(color: Constants.TextBlack),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SvgPicture.asset(
Constants.calender,
width: 22.w,
height: 22.w,
),
SizedBox(width: 12.w),
Text(
'Today',
style: ThemeStyles.NormalBold,
),
],
),
),
),
Expanded(
child: ExcludeSemantics(
child: Obx(
() => SmartRefresher(
// scrollController: _scroll,
controller: paginateController,
enablePullUp: true,
enablePullDown: false,
onLoading: () async {
print("total page loading => ${statsController.analyticsTotalPage.value}");
print("current page loading => ${statsController.analyticsPage.value}");
if (statsController.analyticsTotalPage.value >= statsController.analyticsPage.value) {
await statsController.fetchAnalytics(isFirst: false);
if (statsController.analyticsLoading.isFalse) {
paginateController.loadComplete();
} else {
paginateController.loadFailed();
}
} else {
paginateController.loadNoData();
}
},
child: ListView.builder(
controller: _scroll,
shrinkWrap: true,
padding: EdgeInsets.zero,
physics: const BouncingScrollPhysics(),
itemCount: statsController.analyticsList.length,
itemBuilder: (ctx, index) {
// int count = index + 1;
var item = statsController.analyticsList[index];
return productCard(analyticsDatum: item, index: index);
},
),
),
),
),
),
SizedBox(height: 12.h),
],
),
);
}
// ***** Paid Summary *****
Widget productCard({required AnalyticsDatum analyticsDatum, int? index}) {
// double netSales = saleDatum.amount! - (saleDatum.discount! + saleDatum.tax!) - saleDatum.refundAmount!;
return Padding(
padding: EdgeInsets.only(top: 12.h),
child: Container(
width: Get.width,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.r),
color: Constants.BackgroundSecondary,
),
padding: EdgeInsets.symmetric(vertical: 12.h, horizontal: 8.w),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
SizedBox(
width: Get.width > 600 ? 10.w : 16.w,
height: Get.width > 600 ? 15.h : 16.h,
child: SvgPicture.asset(
Constants.analytic,
// color: Colors.black,
// size: 30.w,
),
),
SizedBox(width: 8.w),
Flexible(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: Get.width * 0.55,
child: Text(
analyticsDatum.productTitle.toString(),
style: ThemeStyles.NormalLightW600,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
),
Text(
"${analyticsDatum.counts} Views",
style: ThemeStyles.NormalLightW600,
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
],
),
),
);
}
void _onChange(DateRangePickerSelectionChangedArgs args) {
print("DateRangePickerSelectionChangedArgs = ${args.value.startDate}");
if (args.value is PickerDateRange) {
statsController.analyticsStartDate.value = args.value.startDate;
statsController.analytiscEndDate.value = args.value.endDate ?? args.value.startDate;
}
}
void _scrollUp() {
if (_scroll!.hasClients){
_scroll!.jumpTo(_scroll!.position.minScrollExtent);
}
}
}
// THIS IS THE CONTROLLER BEING FOUND AT THE START OF THE TAB
class StatsController extends GetxController {
var startDate = DateTime.now().subtract(Duration(days: 6)).obs;
var endDate = DateTime.now().obs;
var analyticsStartDate = DateTime.now().subtract(Duration(days: 6)).obs;
var analytiscEndDate = DateTime.now().obs;
var analyticsLoadingFirst = false.obs;
var analyticsLoading = false.obs;
var analytics = AnalyticsModel().obs;
var analyticsList = <AnalyticsDatum>[].obs;
// ******** Analytics *******
Future<void> fetchAnalytics({required bool isFirst}) async {
if (isFirst == true) {
analyticsLoadingFirst(true);
analyticsPage.value = 1;
analyticsTotalPage.value = 0;
analyticsList.clear();
} else {
analyticsLoading(true);
}
analytics.value = await RemoteServices.getStatsAnalytics(
token: Constants.token!,
start: DateFormat('yyyy-MM-dd').format(analyticsStartDate.value),
end: DateFormat('yyyy-MM-dd').format(analytiscEndDate.value),
page: analyticsPage.value,
);
if (analytics.value.data!.data!.isNotEmpty) {
analyticsList.value = analyticsList.value + analytics.value.data!.data!;
analyticsPage.value += 1;
analyticsTotalPage.value = analytics.value.data!.lastPage!;
}
isFirst == true ? analyticsLoadingFirst(false) : analyticsLoading(false);
}
#override
void onInit() {
fetchAnalytics(isFirst: true);
super.onInit();
}
#override
void onClose() {
startDate.value = DateTime.now().subtract(Duration(days: 7));
endDate.value = DateTime.now();
super.onClose();
}
}
I've tried to look it up in the docs of pull to refresh but found nothing

You may have loaded all the data and called the refreshController.loadNoData() function.
Once you call the refreshController.loadNoData() then onLoading function will not be invoked by the time you call the refreshController.resetNoData().
In short, to get rid of this issue just call the refreshController.resetNoData() in onRefresh function.

Related

Animation library is giving me this error about lifecycle state

Animation library is giving me this error about lifecycle state-- 'package:flutter/src/widgets/framework.dart': Failed assertion: line 4519 pos 12: '_lifecycleState != _ElementLifecycle.defunct': is not true. is was getting this error when i used animation controller.
After the error my flutter application crashed and i have to reload everytime.
// ignore_for_file: depend_on_referenced_packages, sized_box_for_whitespace, avoid_print
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:habbit_app/controllers/timer_tab_controller.dart';
import 'package:habbit_app/screens/timer/set_timer_component.dart';
import 'package:flutter_ringtone_player/flutter_ringtone_player.dart';
import 'package:habbit_app/widgets/text_widget/label_text.dart';
class TimerTab extends StatefulWidget {
const TimerTab({super.key});
#override
State<TimerTab> createState() => _TimerTabState();
}
class _TimerTabState extends State<TimerTab> with TickerProviderStateMixin {
AnimationController? animationController;
TimerTabController tabController =
Get.put(TimerTabController(), permanent: false);
late TabController _controller;
bool isPlaying = false;
// int get countText2 {
// int count = tabController.totalSeconds;
// return;
// }
String get countText {
Duration count =
animationController!.duration! * animationController!.value;
return animationController!.isDismissed
? '${animationController!.duration!.inHours}:${(animationController!.duration!.inMinutes % 60).toString().padLeft(2, '0')}:${(animationController!.duration!.inSeconds % 60).toString().padLeft(2, '0')}'
: '${count.inHours}:${(count.inMinutes % 60).toString().padLeft(2, '0')}:${(count.inSeconds % 60).toString().padLeft(2, '0')}';
}
#override
void dispose() {
animationController!.dispose();
super.dispose();
}
double progress = 1.0;
void notifiy() {
if (countText == "0:00:00") {
FlutterRingtonePlayer.playNotification();
}
}
#override
void initState() {
// ignore: todo
// TODO: implement initState
TimerTabController tabController =
Get.put(TimerTabController(), permanent: false);
super.initState();
_controller = TabController(length: 3, vsync: this);
_controller.addListener(() {
tabController.tabIndex.value = _controller.index;
// print(tabController.tabIndex.value);
});
super.initState();
animationController = AnimationController(
vsync: this, duration: Duration(seconds: tabController.totalSeconds));
animationController!.addListener(() {
notifiy();
if (animationController!.isAnimating) {
setState(() {
progress = animationController!.value;
});
} else {
setState(() {
progress = 1.0;
isPlaying = false;
});
}
});
}
#override
Widget build(BuildContext context) {
var color = Theme.of(context);
return Obx(
() => tabController.isFirst.value
? SetTimerComponent(
changeAnimation: () {
animationController!.duration =
Duration(seconds: tabController.totalSeconds);
tabController.changeFirst();
},
)
: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// start timer screen
Container(
height: 350,
width: MediaQuery.of(context).size.width,
// color: color.cardColor,
child: Column(children: [
Stack(
alignment: Alignment.center,
children: [
SizedBox(
width: 300,
height: 300,
child: CircularProgressIndicator(
color: color.primaryColor,
backgroundColor:
color.disabledColor.withOpacity(0.6),
value: progress,
strokeWidth: 10,
)),
GestureDetector(
onTap: () {
if (animationController!.isDismissed) {
showModalBottomSheet(
context: context,
builder: (context) => Container(
height: 300,
child: CupertinoTimerPicker(
initialTimerDuration:
animationController!.duration!,
backgroundColor:
color.backgroundColor,
onTimerDurationChanged: (time) {
setState(() {
animationController!.duration =
time;
});
},
),
));
}
},
child: AnimatedBuilder(
animation: animationController!,
builder: ( context, child) => Text(
countText,
style: TextStyle(
fontSize: 60,
fontWeight: FontWeight.bold,
color: color.primaryColor),
),
),
),
],
),
]),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
GestureDetector(
onTap: () {
print(tabController.totalSeconds);
if (animationController!.isAnimating) {
animationController!.stop();
setState(() {
isPlaying = false;
});
} else {
animationController!.reverse(
from: animationController!.value == 0
? 1.0
: animationController!.value);
setState(() {
isPlaying = true;
});
}
},
child: Container(
height: 35,
width: 90,
decoration: BoxDecoration(
borderRadius:
const BorderRadius.all(Radius.circular(30)),
color: color.primaryColor),
child: Center(
child: LabelText(
text: isPlaying == true ? "PAUSE" : "RESUME",
isBold: true,
isColor: true,
color: Colors.white,
)),
),
),
GestureDetector(
onTap: () {
animationController!.reset();
tabController.currentvalueHour.value = 0;
tabController.currentvalueMin.value = 0;
tabController.currentvalueSec.value = 0;
setState(() {
isPlaying = false;
});
tabController.changeFirst2();
},
child: Container(
height: 35,
width: 90,
decoration: BoxDecoration(
borderRadius:
const BorderRadius.all(Radius.circular(30)),
color: color.disabledColor.withOpacity(0.5)),
child: const Center(
child: LabelText(
text: "DELETE",
isColor: true,
color: Colors.white,
isBold: true,
)),
),
)
],
)
],
),
);
}
}

How to return the PageView to its initial state in Flutter

I am making a quiz app and at first everything works fine, but when I do a quiz the first time, it does the correct or incorrect answer check perfectly.
But when I go back to quiz without restarting the app just navigating from one page to another the PageView does not reset its state again.
Before taking the quiz
enter image description here
After I do the quiz and I want to do it again without restart the app, I get the checked answers.
enter image description here
How to return the PageView to its initial state without restart the app
Here is my code:
import 'package:flutter/material.dart';
import 'package:quizapp/src/models/quiz_model.dart';
import 'package:quizapp/src/screens/result_screen.dart';
class QuizScreen extends StatefulWidget {
const QuizScreen({Key? key}) : super(key: key);
#override
State<QuizScreen> createState() => _QuizScreenState();
}
class _QuizScreenState extends State<QuizScreen> {
int _questionNumber = 1;
late PageController _controller;
int _score = 0;
#override
void initState() {
_controller = PageController(initialPage: 0);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: PageView.builder(
physics: const NeverScrollableScrollPhysics(),
controller: _controller,
itemCount: questions.length,
itemBuilder: (context, index) {
final _question = questions[index];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 16,
),
Text(
_question.text,
style: const TextStyle(fontSize: 22),
),
const SizedBox(
height: 16,
),
Expanded(
child: SingleChildScrollView(
child: Column(
children: _question.options
.map((option) => GestureDetector(
onTap: () {
Future.delayed(
const Duration(milliseconds: 250),
() {
if (_questionNumber <
questions.length) {
_controller.nextPage(
duration: const Duration(
milliseconds: 250),
curve: Curves.easeInExpo);
setState(() {
if (option.isCorrect == true) {
_score++;
}
});
setState(() {
_questionNumber++;
// _isLocked = false;
});
} else {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) =>
ResultScreen(
score: _score),
));
}
});
if (_question.isLocked) {
return;
} else {
setState(() {
_question.isLocked = true;
_question.selectedOption = option;
});
}
},
child: Container(
height: 50,
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.symmetric(
vertical: 8),
decoration: BoxDecoration(
color: const Color(0xFF6949FD),
borderRadius:
BorderRadius.circular(16),
border: Border.all(
color: getColorForOption(
option, _question))),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
option.text,
style: const TextStyle(
fontSize: 18,
color: Colors.white),
),
const SizedBox(width: 10),
getIconForOption(option, _question)
],
),
),
))
.toList(),
)))
]);
},
)),
),
],
),
));
}
Color getColorForOption(Option option, Question _question) {
final isSelected = option == _question.selectedOption;
if (_question.isLocked) {
if (isSelected) {
return option.isCorrect ? Colors.green : Colors.red;
} else if (option.isCorrect) {
return Colors.green;
}
}
return const Color(0xFF6949FD);
}
Widget getIconForOption(Option option, Question _question) {
final isSelected = option == _question.selectedOption;
if (_question.isLocked) {
if (isSelected) {
return option.isCorrect
? const Icon(Icons.check_circle, color: Colors.green)
: const Icon(Icons.cancel, color: Colors.red);
} else if (option.isCorrect) {
return const Icon(Icons.check_circle, color: Colors.green);
}
}
return const SizedBox.shrink();
}
}
An easier way is to restart the app when you go back or press a button. You can wrap Scaffold() with WillPopScope() to restart when you back. You can use this package to restart.
If you need to save the score, you can save it in local storage. Another easy package for this is get_storage.
dependencies:
flutter_phoenix: ^1.1.0
runApp(Phoenix(child: const MyApp()));
WillPopScope(
onWillPop: () async {
Phoenix.rebirth(context);
},
child: Scaffold())

Scroll end detect to many times in flutter

I uses Listview.builder. it detect scroll end to many time that is why API call to many times and add duplicate Data in Listview.
Code:-
ListView.builder(
controller: _scrollController
..addListener(() async {
if (_scrollController
.position.pixels -
10 ==
_scrollController.position
.maxScrollExtent -
10 &&
!state.isPaginationLoading) {
print("Scroll End TEst Screen");
await ctx
.read<ProfileCubit>()
.getProfiles(
context, true, null);
}
Dont put logic code inside build. In your case _scrollController will addListener every times widget build called, cause multiple handle will trigger.
Advice for you is create and put handle logic to a function, put addListener/removeListener in initState/dispose because they was called only once.
With your problem, you can create a variale to check api was called yet and prevent other call.
class AppState extends State<App> {
var scroll = ScrollController();
var preventCall = false;
#override
initState() {
scroll.addListener(onScroll);
super.initState();
}
#override
void dispose() {
scroll.removeListener(onScroll);
super.dispose();
}
Future yourFuture() async {}
void onScroll() {
var position = scroll.position.pixels;
if (position >= scroll.position.maxScrollExtent - 10) {
if (!preventCall) {
yourFuture().then((_) => preventCall = false);
preventCall = true;
}
}
}
#override
Widget build(BuildContext context) {
return ...
}
}
You can add a condition to check if API call is happening or not and based on it you can you can call the API. You would also need to handle pagination logic if all info is loaded.
you can always check if you reached at the limit of max Limit of your scroll controller then you can call API
condition is like
child: ListView.builder(
controller: _controller
..addListener(() async {
if (_controller.position.pixels >
_controller.position.maxScrollExtent) {
_loadMore();
}
}),
you may be like full example
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
class Paggination2HomeScreen extends StatefulWidget {
const Paggination2HomeScreen({Key? key}) : super(key: key);
#override
State<Paggination2HomeScreen> createState() => _Paggination2HomeScreenState();
}
class _Paggination2HomeScreenState extends State<Paggination2HomeScreen> {
final String _baseUrl = "https://jsonplaceholder.typicode.com/photos";
int _page = 1;
final int _limit = 10;
bool _hasNextPage = true;
bool _isFirstLoadRunning = false;
bool _isLoadMoreRunning = false;
List _posts = [];
void _firstLoad() async {
setState(() {
_isFirstLoadRunning = true;
});
try {
final res = await http.get(
Uri.parse("$_baseUrl?_page=$_page&_limit=$_limit"),
);
if (res.statusCode == 200) {
setState(() {
_posts = jsonDecode(res.body);
});
log('receivedData : ${jsonDecode(res.body)}');
}
} catch (e) {
if (kDebugMode) {
log('Something went wrong');
}
}
setState(() {
_isFirstLoadRunning = false;
});
}
void _loadMore() async {
if (_hasNextPage == true &&
_isFirstLoadRunning == false &&
_isLoadMoreRunning == false) {
setState(() {
_isLoadMoreRunning = true;
});
_page++;
try {
final res = await http.get(
Uri.parse('$_baseUrl?_page=$_page&_limit=$_limit'),
);
log('url : $_baseUrl?_page=$_page&_limit=$_limit');
if (res.statusCode == 200) {
final List fetchedPost = jsonDecode(res.body);
if (fetchedPost.isNotEmpty) {
setState(() {
_posts.addAll(fetchedPost);
});
} else {
setState(() {
_hasNextPage = false;
});
}
}
} catch (e) {
if (kDebugMode) {
log('something went wrong');
}
}
setState(() {
_isLoadMoreRunning = false;
});
}
}
// the controller for listyView
late ScrollController _controller;
#override
void initState() {
super.initState();
_firstLoad();
_controller = ScrollController();
}
#override
void dispose() {
super.dispose();
_controller.removeListener(_loadMore);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.orangeAccent,
elevation: 0.0,
centerTitle: true,
title: Text(
'Paggination',
style: TextStyle(
color: Colors.black.withOpacity(0.52),
fontSize: 20,
),
),
systemOverlayStyle: const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
),
),
body: _isFirstLoadRunning
? Center(
child: CircularProgressIndicator(
color: Colors.black.withOpacity(0.25),
),
)
: Column(
children: [
Expanded(
child: ListView.builder(
controller: _controller
..addListener(() async {
if (_controller.position.pixels >
_controller.position.maxScrollExtent) {
_loadMore();
}
}),
physics: const BouncingScrollPhysics(),
itemCount: _posts.length,
itemBuilder: (context, index) => Container(
padding: const EdgeInsets.all(10),
margin: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 5,
),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.065),
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: Colors.black.withOpacity(0.2),
width: 0.5,
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'ID : ${_posts.elementAt(index)['id']}',
),
Text(
'AlbumID : ${_posts.elementAt(index)['albumId']}',
),
Text(
'Title : ${_posts.elementAt(index)['title']}',
textAlign: TextAlign.justify,
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
height: MediaQuery.of(context).size.width * 0.4,
width: MediaQuery.of(context).size.width * 0.4,
child: Image.network(
_posts.elementAt(index)['url'],
),
),
SizedBox(
height: MediaQuery.of(context).size.width * 0.4,
width: MediaQuery.of(context).size.width * 0.4,
child: Image.network(
_posts.elementAt(index)['thumbnailUrl'],
),
),
],
),
],
),
),
),
),
// when the _loadMore running
if (_isLoadMoreRunning == true)
Container(
color: Colors.transparent,
padding: const EdgeInsets.only(top: 10, bottom: 20),
child: const Center(
child: CircularProgressIndicator(
color: Colors.amberAccent,
strokeWidth: 3,
backgroundColor: Colors.transparent,
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
),
),
),
if (_hasNextPage == false)
SafeArea(
child: Container(
width: double.maxFinite,
padding: const EdgeInsets.only(top: 20, bottom: 20),
color: Colors.orangeAccent,
child: const Text('you get all'),
),
)
],
),
);
}
}

How to save page state on revisit in flutter

I have 2 screens and I am trying to achieve understand how to achieve page state. For example, in the below screen, I have 4 options, and all of them take the user to the same screen the only difference is API getting called for each of them is different to build a list. I am trying to handle back arrow action, and that's where I am having issues.
Use Case -
When user is on screen 2 he is playing the song, now on back press song continues to play. Now when the user again chooses the same option from screen 1 I want to show the same list without reload and selection. If user selects any other option it should behave normal, which is working.
Solution -
I can hardcode loaded songs list and send back to screen 1 and then again get that back with the selection but this will take lot of resources.
AutomaticKeepAliveClientMixin i tried this tutorial but that didn't help or kept state active.
PageStorage is 3rd option i saw but i found majority of the time this is being used on app where we have a tab.
Screen 1 -
class Dashboard extends StatefulWidget {
int playingId;
Dashboard({this.playingId});
#override
_DashboardState createState() => _DashboardState(playingId);
}
class _DashboardState extends State<Dashboard> {
String appname;
int playingId = 0;
_DashboardState(this.playingId);
// print('${playingId}');
#override
void initState() {
appname="";
// playingId=0;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0,
backgroundColor: AppColors.darkBlue,
centerTitle: true,
title: Text("Tirthankar",
style: TextStyle(color: Colors.white),),
),
backgroundColor: AppColors.styleColor,
body: Column(
children: <Widget>[
// SizedBox(height: 5),
Padding(
padding: const EdgeInsets.all(20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
CustomGridWidget(
child: Icon(
Icons.file_download,
size: 100,
color: AppColors.styleColor,
),
// image: 'assets/bhaktambar.png',
sizew: MediaQuery.of(context).size.width * .4,
sizeh: MediaQuery.of(context).size.width * .5,
borderWidth: 2,
label: "Bhakti",
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => ListPage(appname: "Kids",playingId: playingId,),
),
);
},
),
CustomGridWidget(
child: Icon(
Icons.file_download,
size: 100,
color: AppColors.styleColor,
),
// image: 'assets/bhaktambar.png',
sizew: MediaQuery.of(context).size.width * .4,
sizeh: MediaQuery.of(context).size.width * .5,
borderWidth: 2,
label: "Kids",
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => ListPage(appname: "Kids",playingId: playingId,),
),
);
},
),
],
),
),
Padding(
padding: const EdgeInsets.only(
left: 20,
right: 20,
bottom: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
CustomGridWidget(
child: Icon(
Icons.favorite,
size: 100,
color: AppColors.styleColor,
),
// image: 'assets/kids.jpg',
sizew: MediaQuery.of(context).size.width * .4,
sizeh: MediaQuery.of(context).size.width * .5,
borderWidth: 2,
label: "Favorite",
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => ListPage(appname: "Songs"),
),
);
},
),
Align(
alignment: Alignment.center,
child: CustomGridWidget(
child: Icon(
Icons.book,
size: 100,
color: AppColors.styleColor,
),
// image: 'assets/vidyasagar.jpg',
sizew: MediaQuery.of(context).size.width * .4,
sizeh: MediaQuery.of(context).size.width * .5,
borderWidth: 2,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => ListPage(appname: "Bhajan"),
),
);
},
),
),
],
),
),
]
),
);
}
Material boxTiles(IconData icon, String name){
return Material(
color: AppColors.mainColor,
elevation: 14.0,
shadowColor: AppColors.styleColor,
borderRadius: BorderRadius.circular(24.0),
child: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child:Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
InkWell(
onTap: (){print("tapped"); /* or any action you want */ },
child: Container(
width: 130.0,
height: 10.0,
color : Colors.transparent,
), // container
), //
//Text
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(name,
style:TextStyle(
color: AppColors.styleColor,
fontSize: 20.0,
)
),
),
//Icon
Material(
color: AppColors.styleColor,
borderRadius: BorderRadius.circular(50.0),
child: Padding(padding: const EdgeInsets.all(10.0),
child: Icon(icon, color: AppColors.lightBlue,size: 30,),
),
),
],
)
],
)
),
)
);
}
}
Screen 2 -
// import 'dart:js';
class ListPage extends StatefulWidget {
String appname;
int playingId;
ListPage({this.appname,this.playingId});
#override
_ListPageState createState() => _ListPageState(appname,playingId);
}
class _ListPageState extends State<ListPage>
with SingleTickerProviderStateMixin,AutomaticKeepAliveClientMixin<ListPage> {
String appname;
int playingId;
bool isPlaying = false;
_ListPageState(this.appname,playingId);
// List<MusicModel> _list1;
List<MusicData> _list;
var _value;
int _playId;
int _songId;
String _playURL;
bool _isRepeat;
bool _isShuffle;
bool _isFavorite;
String _startTime;
String _endTime;
AnimationController _controller;
Duration _duration = new Duration();
Duration _position = new Duration();
final _random = new Random();
AudioPlayer _audioPlayer = AudioPlayer();
#override
void initState() {
_playId = 0;
// _list1 = MusicModel.list;
this._fileUpdate();
// _list = _list1;
_controller =
AnimationController(vsync: this, duration: Duration(microseconds: 250));
_value = 0.0;
_startTime = "0.0";
_endTime = "0.0";
_isRepeat = false;
_isShuffle = false;
_isFavorite = false;
_audioPlayer.onAudioPositionChanged.listen((Duration duration) {
setState(() {
// _startTime = duration.toString().split(".")[0];
_startTime = duration.toString().split(".")[0];
_duration = duration;
// _position = duration.toString().split(".")[0];
});
});
_audioPlayer.onDurationChanged.listen((Duration duration) {
setState(() {
_endTime = duration.toString().split(".")[0];
_position = duration;
});
});
_audioPlayer.onPlayerCompletion.listen((event) {
setState(() {
isPlaying = false;
_position = _duration;
if (_isRepeat) {
_songId = _songId;
} else {
if (_isShuffle) {
var element = _list[_random.nextInt(_list.length)];
_songId = element.id;
} else {
_songId = _songId + 1;
}
}
_player(_songId);
});
});
super.initState();
}
bool get wantKeepAlive => true;
#override
Widget build(BuildContext context) {
super.build(context);
return Scaffold(
appBar: AppBar(
elevation: 0,
backgroundColor: AppColors.mainColor,
centerTitle: true,
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: (){
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => Dashboard(playingId: _songId),
),
);
},
),
title: Text(
appname,
style: TextStyle(color: AppColors.styleColor),
),
),
backgroundColor: AppColors.mainColor,
body: Stack(
children: <Widget>[
Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(24.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
CustomButtonWidget(
child: Icon(
Icons.favorite,
color: AppColors.styleColor,
),
size: 50,
onTap: () {},
),
CustomButtonWidget(
image: 'assets/logo.jpg',
size: 100,
borderWidth: 5,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => DetailPage(),
),
);
},
),
CustomButtonWidget(
child: Icon(
Icons.menu,
color: AppColors.styleColor,
),
size: 50,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => HomePage(),
),
);
},
)
],
),
),
slider(),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(
icon: Icon(
_isRepeat ? Icons.repeat_one : Icons.repeat,
color: _isRepeat ? Colors.brown : AppColors.styleColor,
),
onPressed: () {
if (_isRepeat) {
_isRepeat = false;
} else {
_isRepeat = true;
}
},
),
IconButton(
icon: Icon(
isPlaying ? Icons.pause : Icons.play_arrow,
color: AppColors.styleColor,
),
onPressed: () {
if (isPlaying) {
_audioPlayer.pause();
setState(() {
isPlaying = false;
});
} else {
if (!isPlaying){
_audioPlayer.resume();
setState(() {
isPlaying = true;
});
}
}
}),
IconButton(
icon: Icon(
Icons.stop,
color: AppColors.styleColor,
),
onPressed: () {
if (isPlaying){
_audioPlayer.stop();
setState(() {
isPlaying = false;
_duration = new Duration();
});
}
// isPlaying = false;
}),
IconButton(
icon: Icon(
Icons.shuffle,
color:
_isShuffle ? Colors.brown : AppColors.styleColor,
),
onPressed: () {
if (_isShuffle) {
_isShuffle = false;
} else {
_isShuffle = true;
}
}),
],
),
),
Expanded(
//This is added so we can see overlay else this will be over button
child: ListView.builder(
physics:
BouncingScrollPhysics(), //This line removes the dark flash when you are at the begining or end of list menu. Just uncomment for
// itemCount: _list.length,
itemCount: _list == null ? 0 : _list.length,
padding: EdgeInsets.all(12),
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
_songId = index;
_player(index);
},
child: AnimatedContainer(
duration: Duration(milliseconds: 500),
//This below code will change the color of sected area or song being played.
decoration: BoxDecoration(
color: _list[index].id == _playId
? AppColors.activeColor
: AppColors.mainColor,
borderRadius: BorderRadius.all(
Radius.circular(20),
),
),
//End of row color change
child: Padding(
padding: const EdgeInsets.all(
16), //This will all padding around all size
child: Row(
mainAxisAlignment: MainAxisAlignment
.spaceBetween, //This will allign button to left, else button will be infront of name
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
_list[index].title,
style: TextStyle(
color: AppColors.styleColor,
fontSize: 16,
),
),
Text(
_list[index].album,
style: TextStyle(
color: AppColors.styleColor.withAlpha(90),
fontSize: 16,
),
),
],
),
IconButton(
icon: Icon(_isFavorite
? Icons.favorite
: Icons.favorite_border),
onPressed: () {
if (_isFavorite) {
_isFavorite = false;
} else {
_isFavorite = true;
}
})
//Diabled Play button and added fav button.
// CustomButtonWidget(
// //This is Play button functionality on list page.
// child: Icon(
// _list[index].id == _playId
// ? Icons.pause
// : Icons.play_arrow,
// color: _list[index].id == _playId
// ? Colors.white
// : AppColors.styleColor,
// ),
// size: 50,
// isActive: _list[index].id == _playId,
// onTap: () async {
// _songId = index;
// _player(index);
// },
// )
],
),
),
),
);
},
),
)
],
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
height: 50,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppColors.mainColor.withAlpha(0),
AppColors.mainColor,
],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
)),
),
)
],
),
// floatingActionButton: FloatingActionButton(
// child: Icon(Icons.music_note),
// onPressed: () async { // String filePath = await FilePicker.getFilePath();
// int status = await _audioPlayer.play("https://traffic.libsyn.com/voicebot/Jan_Konig_on_the_Jovo_Open_Source_Framework_for_Voice_App_Development_-_Voicebot_Podcast_Ep_56.mp3");
// if (status == 1){
// setState(() {
// isPlaying = true;
// });
// }
// },
// )
);
}
Widget slider() {
return Slider(
activeColor: AppColors.styleColor,
inactiveColor: Colors.lightBlue,
value: _duration.inSeconds.toDouble(),
min: 0.0,
max: _position.inSeconds.toDouble(),
divisions: 10,
onChangeStart: (double value) {
print('Start value is ' + value.toString());
},
onChangeEnd: (double value) {
print('Finish value is ' + value.toString());
},
onChanged: (double value) {
setState(() {
seekToSecond(value.toInt());
value = value;
});
});
}
Future<Void> _fileUpdate() async {
String url =
"azonaws.com/input.json";
String arrayObjsText = "";
try {
eos.Response response;
Dio dio = new Dio();
response = await dio.get(url,options: Options(
responseType: ResponseType.plain,
),);
arrayObjsText = response.data;
print(response.data.toString());
} catch (e) {
print(e);
}
var tagObjsJson = jsonDecode(arrayObjsText)['tags'] as List;
this.setState(() {
_list = tagObjsJson.map((tagJson) => MusicData.fromJson(tagJson)).toList();
});
// return _list;
// print(_list);
}
Future<void> _player(int index) async {
if (isPlaying) {
if (_playId == _list[index].id) {
int status = await _audioPlayer.pause();
if (status == 1) {
setState(() {
isPlaying = false;
});
}
} else {
_playId = _list[index].id;
_playURL = _list[index].songURL;
_audioPlayer.stop();
int status = await _audioPlayer.play(_playURL);
if (status == 1) {
setState(() {
isPlaying = true;
});
}
}
} else {
_playId = _list[index].id;
_playURL = _list[index].songURL;
int status = await _audioPlayer.play(_playURL);
if (status == 1) {
setState(() {
isPlaying = true;
});
}
}
}
String _printDuration(Duration duration) {
String twoDigits(int n) {
if (n >= 10) return "$n";
return "0$n";
}
String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
return "${twoDigits(duration.inHours)}:$twoDigitMinutes:$twoDigitSeconds";
}
void seekToSecond(int second) {
Duration newDuration = Duration(seconds: second);
_audioPlayer.seek(newDuration);
}
}
PageStorage is 3rd option i saw but i found majority of the time this is being used on app where we have a tab.
You still can use this approach with what you want, with or without Tab/ bottom navigation bar.
PageViewClass
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> with SingleTickerProviderStateMixin{
PageController _pageController;
#override
void initState() {
super.initState();
_pageController = PageController();
}
#override
void dispose() {
super.dispose();
_pageController?.dispose();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: PageView(
controller: _pageController,
physics: NeverScrollableScrollPhysics(), // so the user cannot scroll, only animating when they select an option
children: <Widget>[
Dashboard(playingId: 1, key: PageStorageKey<String>('MyPlayList'), pageController: _pageController), //or the name you want, but you need to give them a key to all the child so it can save the Scroll Position
ListPage(appname: "FirstButton",playingId: 1, key: PageStorageKey<String>('FirstButton'), pageController: _pageController),
ListPage(appname: "SecondButton",playingId: 1, key: PageStorageKey<String>('SecondButton'), pageController: _pageController),
ListPage(appname: "ThirdButton",playingId: 1, key: PageStorageKey<String>('ThirdButton'), pageController: _pageController),
ListPage(appname: "FourthButton",playingId: 1, key: PageStorageKey<String>('FourthButton'), pageController: _pageController)
],
),
)
);
}
}
Now you pass the PageController to all children (add a key and PageController attribute to Screen 1 and 2) and in DashBoard (Screen 1) you can change the onTap of the buttons from the MaterialRoute to this
onTap: () => widget.pageController.jumpToPage(x), //where x is the index of the children of the PageView you want to see (between 0 and 4 in this case)
In Screen 2 you wrap all your Scaffold with a WillPopScope so when the user tap back instead of closing the route it goes back to the Dashboard Widget at index 0
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () {
widget.pageController.jumpToPage(0); // Move back to dashboard (index 0)
return false;
}
child: Scaffold(...)
);
}
You can use other methods of PageController if you want some effects like animations (animateTo, nextPage, previousPage, etc.)

Flutter: Multiple Instances of Shared Preferences?

I am having a problem with SharedPreferences and multiple Modules I am generating on a Form using ListView.builder
The form is basically asking for some parents details and their childs details - by default the form assumes the parent has one child, but more can be added by clicking a button. The ChildModule has the ability to "close" but when "re-opened" the data doesn't persist, hence using SharedPreferences, it works fine with one Child, but once a second child is added it seems to be creating multiple Instances of SharedPreferences.
I have cut out everything to show what I am trying to achieve. NOTE this is being used as a Web App if it matters.
Oh and ChildModule needs to have its own state because it has a widget which requires it (not shown)
ENQUIRY FORM
final GlobalKey<FormBuilderState> _enquiryFormKey = GlobalKey<FormBuilderState>();
class EnquiryForm extends StatefulWidget {
static List<int> numberOfChildren = [1];
#override
_EnquiryFormState createState() => _EnquiryFormState();
}
class _EnquiryFormState extends State<EnquiryForm> {
int defaultNumberOfChildren = 1;
removeModule(){
setState(() {});
}
#override
Widget build(BuildContext context) {
return FormBuilder(
key: _enquiryFormKey,
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: CustomTextField(
label: 'Parent First Name',
isRequired: true,
),
),
),
//ChildModuleList
ListView.builder(
shrinkWrap: true,
itemCount: EnquiryForm.numberOfChildren.length,
itemBuilder: (context,int index){
return ChildModule(EnquiryForm.numberOfChildren[index], removeModule);
}
),
SizedBox(height: 20,),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
ThemedButton(
onPressed: (){
setState(() {
defaultNumberOfChildren++;
EnquiryForm.numberOfChildren.add(defaultNumberOfChildren);
});
},
child: Text(
'Add Additional Child',
style: TextStyle(color: Colors.white),)
),
SizedBox(width: 10,),
ThemedButton(
onPressed: (){},
child: Text('Enquire Now', style: TextStyle(color: Colors.white),))
],
)
],
));
}
}
CHILD MODULE
class ChildModule extends StatefulWidget {
final int number;
final Function() callback;
ChildModule(this.number,this.callback);
#override
_ChildModule createState() => _ChildModule();
}
class _ChildModule extends State<ChildModule> {
SharedPreferences childModuleData;
String firstName;
bool loading = true;
bool isOpen;
#override
void initState() {
print('this module number is ${widget.number}');
_spInstance();
isOpen = true;
super.initState();
}
Future<void> _spInstance() async {
if(childModuleData == null && widget.number == 1) {
childModuleData = await SharedPreferences.getInstance();
print('got instance');
} else {
print('broken');
print(childModuleData);
};
String _testValue = childModuleData.getString('Child First Name');
if(_testValue == null){
childModuleData.setString('Child First Name', '');
loading = false;
} else {
childModuleData.clear();
_spInstance();
}
}
#override
Widget build(BuildContext context) {
return loading ? Loading() : Column(
children: <Widget>[
GestureDetector(
onTap: () {
setState(() {
if (isOpen == false) {
isOpen = true;
} else {
isOpen = false;
}
});
},
child: Container(
height: 40,
padding: EdgeInsets.only(left: 10, right: 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(4.0)),
color: Colors.blue[50],
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
'Child Details',
style: TextStyle(color: Colors.blue[300], fontSize: 16),
),
Row(
children: <Widget>[
isOpen
? Icon(
Icons.arrow_drop_down,
size: 30,
)
: Transform.rotate(
angle: math.pi / 2,
child: Icon(
Icons.arrow_drop_down,
size: 30,
),
),
widget.number > 1
? IconButton(icon: Icon(Icons.clear), onPressed: () async {
await FormFunctions().removeModule(widget.number, EnquiryForm.numberOfChildren);
widget.callback();
})
: Container(),
],
),
],
),
),
),
AnimatedContainer(
duration: Duration(seconds: 2),
curve: Curves.fastOutSlowIn,
padding: EdgeInsets.fromLTRB(10, 5, 10, 5),
height: isOpen ? null : 0,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey[300]),
borderRadius: BorderRadius.all(Radius.circular(4.0)),
),
child: Column(
children: <Widget>[
Row(
children: <Widget>[
CustomTextField(
label: fieldFirstName,
isRequired: true,
initalValue: childModuleData.getString(fieldFirstName) ?? '',
onChanged: (value){
childModuleData.setString(fieldFirstName, value);
},
),
],
),
],
),
),
],
);
}
}
CONSOLE ERROR AFTER SECOND MODULE IS CREATED
Launching lib/main.dart on Chrome in debug mode...
Syncing files to device Chrome...
Debug service listening on ws://127.0.0.1:58490/EkMAy9CGY74=
Debug service listening on ws://127.0.0.1:58490/EkMAy9CGY74=
this module number is 1
got instance
Instance of 'SharedPreferences'
null
this module number is 2 //Number 2 Module is created
broken
null
null
TypeError: Cannot read property 'getString' of null
at child_module._ChildModule.new._spInstance$ (http://localhost:58433/packages/webenrol/shared/widgets/day_button.dart.lib.js:3704:47)
at _spInstance$.next (<anonymous>)
at runBody (http://localhost:58433/dart_sdk.js:43121:34)
at Object._async [as async] (http://localhost:58433/dart_sdk.js:43149:7)
at child_module._ChildModule.new.[_spInstance] (http://localhost:58433/packages/webenrol/shared/widgets/day_button.dart.lib.js:3694:20)
at child_module._ChildModule.new.initState (http://localhost:58433/packages/webenrol/shared/widgets/day_button.dart.lib.js:3689:24)
at framework.StatefulElement.new.[_firstBuild] (http://localhost:58433/packages/flutter/src/widgets/widget_span.dart.lib.js:41219:58)
at framework.StatefulElement.new.mount (http://localhost:58433/packages/flutter/src/widgets/widget_span.dart.lib.js:12605:24)
at framework.SingleChildRenderObjectElement.new.inflateWidget (http://localhost:58433/packages/flutter/src/widgets/widget_span.dart.lib.js:11420:16)
in your case the concern is at the level of recording data in shared preferences. one solution would be to add the widget number in the key to save like this (await SharedPreferences.getInstance()).setString("Your Key${widgetNumber}");
and edit your function _spInstance
class ChildModule extends StatefulWidget {
final int number;
final Function() callback;
ChildModule(this.number,this.callback);
#override
_ChildModule createState() => _ChildModule();
}
class _ChildModule extends State<ChildModule> {
SharedPreferences childModuleData;
...
Future<void> _spInstance() async {
if(childModuleData == null) {
childModuleData = await SharedPreferences.getInstance();
print('got instance');
}
String _testValue = childModuleData.getString('Child First Name${widget.number}');
//NB: do not clear data on chlidModuleData any more.
...
}
...
}