Flutter - Getx Value updates only after hot reload - flutter

Consider the following code
class HomeScreen extends StatelessWidget {
const HomeScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return GetBuilder<HomeController>(
init: HomeController(),
builder: (controller) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
phoneWidget(),
TextFormField(
controller: controller.formController,
),
ElevatedButton(onPressed: ()=> controller.clearForm(), child: const Text('Clear'))
],
),
),
);
});
}
Widget phoneWidget() {
final HomeController _controller = Get.find();
return Container(
decoration: BoxDecoration(
color: Colors.amber,
borderRadius: BorderRadius.circular(20)
),
child: IntlPhoneField( // the widget in question
controller:_controller.secondController ,
showCountryFlag: false,
iconPosition: IconPosition.trailing,
autoValidate: false,
initialValue: _controller.initialPhoneNumber,
initialCountryCode: _controller.initialCountryCode,
),
);
}
}
In the above i have a widget. which I'm setting initial values at init on controller.
class HomeController extends GetxController{
final _intialCountryCode = ''.obs;
String get initialCountryCode => _intialCountryCode.value;
final _initialPhoneNumber = ''.obs;
String get initialPhoneNumber => _initialPhoneNumber.value;
final formController = TextEditingController();
final secondController = TextEditingController();
#override
void onInit() {
super.onInit();
_intialCountryCode('IN');
_initialPhoneNumber('123457788');
formController.text = "TEST";
secondController.text = '1234944627';
}
clearForm(){
formController.clear();
secondController.clear();
secondController.text = '123349526';
}
}
I'm using Getx for my application. I'm assigning values for this widget on init. What I'm expecting is to the widget shows the initial value when the screen is loaded. How ever the changes are not reflected on the widget, instead if I hot-reload, the changes are updated on the widget. I have tried wrapping the widget with Obx. but the results are same. The changes are made only after hot reload. What causes this? Why does the widget only updates after hot reload? How can i resolve this properly

class HomeController extends GetxController{
final _intialCountryCode = ''.obs;
String get initialCountryCode => _intialCountryCode.value;
final _initialPhoneNumber = ''.obs;
String get initialPhoneNumber => _initialPhoneNumber.value;
final formController = TextEditingController().obs;
final secondController = TextEditingController().obs;
#override
void onInit() {
super.onInit();
_intialCountryCode('IN');
_initialPhoneNumber('123457788');
formController.text = "TEST";
secondController.text = '1234944627';
}
clearForm(){
formController.clear();
secondController.clear();
secondController.text = '1234944627';
}
}
GetX<HomeController>(
init: HomeController(),
builder: (controller) {
try adding this changes and see if it works

Related

Flutter - Accessing the String value of the dropdown button from the another widget with Provider

I would like to get the value of the dropdown from the other widget in the real estate app. Say I have two widgets. First one is the dropdown widget, and the second one is Add New Property widget (or a page).. I would like to access the value of the dropdown from the Add New Property.
I could achieve this with final Function onChanged; but Im wondering if there is another way to achieve with the Provider package or the ValueNotifier
the code below is my Dropdown button widget
class PropertyType extends StatefulWidget {
final Function onChanged;
const PropertyType({
super.key,
required this.onChanged,
});
#override
State<PropertyType> createState() => _PropertyTypeState();
}
class _PropertyTypeState extends State<PropertyType> {
final List<String> _propertyTypeList = propertyType;
String? _propertyType = 'No Info';
#override
Widget build(BuildContext context) {
return ANPFormContainer(
fieldTitle: 'Property Type',
subTitle: 'အိမ်ခြံမြေအမျိုးအစား',
child: FormBuilderDropdown<String>(
name: 'a2-propertyType',
initialValue: _propertyType,
items: _propertyTypeList
.map(
(itemValue) => DropdownMenuItem(
value: itemValue,
child: Text(itemValue),
),
)
.toList(),
onChanged: (val) {
setState(() {
_propertyType = val;
widget.onChanged(val);
});
},
),
);
}
}
And this is the "Add New Property" form page
class ANPTest extends StatefulWidget {
const ANPTest({super.key});
#override
State<ANPTest> createState() => _ANPTestState();
}
class _ANPTestState extends State<ANPTest> {
final TextEditingController _propertyid = TextEditingController();
String _propertyType = 'No Info';
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: ZayyanColorTheme.zayyanGrey,
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
PropertyID(propertyID: _propertyid),
PropertyType(onChanged: (String value) {
_propertyType = value;
}),
addVerticalSpacer(25),
ANPNextButton(onPressed: _onpressed),
],
),
),
);
}
_onpressed() {
final anp = MdlFirestoreData(
propertyid: _propertyid.text, propertyType: _propertyType)
.toFirestore();
FirebaseFirestore.instance.collection('Selling Posts').add(anp);
}
}
Thank you for helping me out.
Best
yes, you could use Getx or provider package by creating a controller(function) and the package helps you to have access to variables in
your controller to use them everywhere in your program,
you may need to learn about Getx
it can help you manage your navigation and state

Can't get GetX work with Widget that do not have direct access to controller

GetX works fine and update data if element have controller. But how to get it work if we have not direct to controller and widget done in way of changing date with onChange.
I created small copy-paste example:
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:web_date_picker/web_date_picker.dart';
void main() {
Get.put(SearchFormController());
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'GetX Demo',
home: MyHomePage(),
);
}
}
class SearchFormController extends GetxController {
var endDate = Rxn<DateTime?>();
setToNow() { // This function should set widget value
endDate.value = DateTime.now();
print('setToNow event: ${endDate.value}');
}
}
class MyHomePage extends StatelessWidget {
var ctrl = Get.find<SearchFormController>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Row(
children: [
Obx(() => SizedBox(
width: 160,
child: WebDatePicker(
initialDate: ctrl.endDate.value,
onChange: (value) {
if (value != null) {
ctrl.endDate.value = value;
print('onChange event: ${ctrl.endDate.value}');
}
},
),
)),
ElevatedButton(
onPressed: () {
ctrl.setToNow();
},
child: Text("Set Date"),
),
],
),
);
}
}
pubspec.yaml:
dependencies:
flutter:
sdk: flutter
web_date_picker: ^1.0.0+3
get: ^4.6.5
In this code I can't set date by clicking on Set Date button.
I looked at widget code and it's controller is hidden with follow realization:
class _WebDatePickerState extends State<WebDatePicker> {
final FocusNode _focusNode = FocusNode();
late OverlayEntry _overlayEntry;
final LayerLink _layerLink = LayerLink();
final _controller = TextEditingController();
late DateTime? _selectedDate;
late DateTime _firstDate;
late DateTime _lastDate;
bool _isEnterDateField = false;
#override
void initState() {
super.initState();
_selectedDate = widget.initialDate;
_firstDate = widget.firstDate ?? DateTime(2000);
_lastDate = widget.lastDate ?? DateTime(2100);
if (_selectedDate != null) {
_controller.text = _selectedDate?.parseToString(widget.dateformat) ?? '';
}
_focusNode.addListener(() {
if (_focusNode.hasFocus) {
_overlayEntry = _createOverlayEntry();
Overlay.of(context)?.insert(_overlayEntry);
} else {
_controller.text = _selectedDate.parseToString(widget.dateformat);
widget.onChange.call(_selectedDate);
_overlayEntry.remove();
}
});
}
void onChange(DateTime? selectedDate) {
_selectedDate = selectedDate;
_controller.text = _selectedDate.parseToString(widget.dateformat);
_focusNode.unfocus();
}
...
Widget code https://github.com/duchdtran/web_date_picker/blob/master/lib/src/web_date_picker.dart#L98
Could anybody provide example how to get button work to set widget value?
I experienced similar problems and solved them desribed as below.
It seems WebDatePicker does not process the value change. Try putting it in its own StatelessWidget:
class MyWebDatePicker extends StatelessClass {
final DateTime dt;
var ctrl = Get.find<SearchFormController>();
MyWebDatePicker(this.dt);
Widget build(BuildContext context) {
return WebDatePicker(
initialDate: dt,
onChange: (value) {
if (value != null) {
ctrl.endDate.value = value;
print('onChange event: ${value}');
}
},
);
}
}
Then, call it like given below. This code looks unusual, but it forces Obx to call MyWebDatePicker with the current DateTime value:
Obx(() => SizedBox(
width: 160,
child: MyWebDatePicker(ctrl.endDate.value)
)
)

Flutter centralized/common loading screen for entire Application

I am working in Riverpod Auth flow boilerplate application.
I want to use common loading screen for all async function even login and logout. Currently I have AppState provider if Appstate loading i show loading screen. it's working fine for login but i wonder it’s good way or bad way.
Can i use this loading screen for all async task in the App?
AuthWidget:
class AuthWidget extends ConsumerWidget {
const AuthWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, WidgetRef ref) {
AppState appState = ref.watch(appStateProvider);
if(appState.isLoading){
return const Center(child: CircularProgressIndicator(color: Colors.red),);
}
return appState.isAuthenticated ? const HomePage() : const SignIn();
}
}
AppState:
class AppState {
User? user;
bool isLoading;
bool isAuthenticated;
AppState(this.user, this.isLoading, this.isAuthenticated);
}
AuthRepository:
class AuthRepository extends StateNotifier<AppState>{
AuthRepository() : super(AppState(null,false,false));
Future<void> signIn()async {
state = AppState(null,true,false);
await Future.delayed(const Duration(seconds: 3));
User user = User(userName: 'FakeUser', email: 'user#gmail.com');
AppState appState = AppState(user, false, true);
state = appState;
}
}
final appStateProvider = StateNotifierProvider<AuthRepository,AppState>((ref){
return AuthRepository();
});
To answer your question : Yes you can.
The only thing I'd change here is the content of your AppState : I'd use a LoadingState dedicated to trigger your Loader instead.
Here is how I like to manage screens with a common loader in my apps.
1 - Create a LoadingState and provide it
final loadingStateProvider = ChangeNotifierProvider((ref) => LoadingState());
class LoadingState extends ChangeNotifier {
bool isLoading = false;
void startLoader() {
if (!isLoading) {
isLoading = true;
notifyListeners();
}
}
void stopLoader() {
if (isLoading) {
isLoading = false;
notifyListeners();
}
}
}
2 - Define a base page with the "common" loader
class LoadingContainer extends ConsumerWidget {
const LoadingContainer({
Key? key,
required this.child,
}) : super(key: key);
final Widget child;
#override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(loadingStateProvider);
return Stack(
children: [
child,
if (state.isLoading)
const Center(child: CircularProgressIndicator())
else
const SizedBox(),
],
);
}
}
3 - Implement this widget whenever I need to handle loading datas.
return Scaffold(
backgroundColor: AppColor.blue,
body: LoadingContainer(
child: ...
And then I simply have to update my loadingStateProvider and it's isLoading value from a Controller or the Widget directly
If you want a centralized/common async calls, the InheritedWidget is ideal for that, you can just add a method and call it from anywhere down stream and because the call is offloaded with async, you can attach extra arguments and add usefull functionality such as a live update instead of relying on stuff like .then(). This example might not be as simple as FDuhen's but you can mix them together if you want to not use keys
AppState now is a widget and contains trigers that rely on global keys to rebuild the correct components, here i assumed that you actualy want to have an common overlay and not a loading screen widget, if not using a Navigator would be batter
Using keys is specially good if you end up implementing something this line, <token> been just a number that references a group of widgets
key: AppState.of(ctx).rebuild_on_triger(<token>)
class App_State_Data {
GlobalKey? page_key;
bool is_logged = false;
bool loading_overlay = false;
String loading_message = '';
}
class AppState extends InheritedWidget {
final App_State_Data _state;
bool get is_logged => _state.is_logged;
bool get should_overlay => _state.loading_overlay;
String get loading_message => _state.loading_message;
void page_rebuild() {
(_state.page_key!.currentState as _Page_Base).rebuild();
}
GlobalKey get page_key {
if (_state.page_key == null) {
_state.page_key = GlobalKey();
}
return _state.page_key!;
}
void place_overlay(String msg) {
_state.loading_message = msg;
_state.loading_overlay = true;
page_rebuild();
}
void clear_overlay() {
_state.loading_message = '';
_state.loading_overlay = false;
page_rebuild();
}
Future<void> triger_login(String message) async {
place_overlay(message);
await Future.delayed(const Duration(seconds: 2));
_state.is_logged = true;
clear_overlay();
}
Future<void> triger_logout(String message) async {
place_overlay(message);
await Future.delayed(const Duration(seconds: 1));
_state.is_logged = false;
clear_overlay();
}
AppState({Key? key, required Widget child})
: this._state = App_State_Data(),
super(key: key, child: child);
static AppState of(BuildContext ctx) {
final AppState? ret = ctx.dependOnInheritedWidgetOfExactType<AppState>();
assert(ret != null, 'No AppState found!');
return ret!;
}
#override
bool updateShouldNotify(AppState old) => true;
}
Here i added it as the topmost element making it like a global data class with is not necessary, you can split the state content and add just the necessary to where its needed
void main() => runApp(AppState(child: App()));
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
#override
Widget build(BuildContext ctx) {
return MaterialApp(
home: Scaffold(
body: Page_Base(
key: AppState.of(ctx).page_key,
),
),
);
}
}
class Page_Base extends StatefulWidget {
final GlobalKey key;
const Page_Base({
required this.key,
}) : super(key: key);
#override
_Page_Base createState() => _Page_Base();
}
class _Page_Base extends State<Page_Base> {
Widget build_overlay(BuildContext ctx) {
return Center(
child: Container(
width: double.infinity,
height: double.infinity,
color: Color(0xC09E9E9E),
child: Center(
child: Text(AppState.of(ctx).loading_message),
),
),
);
}
#override
Widget build(BuildContext ctx) {
return Stack(
children: [
AppState.of(ctx).is_logged ? Page_Home() : Page_Login(),
AppState.of(ctx).should_overlay ? build_overlay(ctx) : Material(),
],
);
}
void rebuild() {
// setState() is protected and can not be called
// from outside of the this. scope
setState(() => null);
}
}
Using AppState is the best part, just because the widget does not have to call more than 1 function and it will rebuild with the correct data on complition
class Page_Login extends StatelessWidget {
const Page_Login({Key? key}) : super(key: key);
#override
Widget build(BuildContext ctx) {
return Center(
child: InkWell(
onTap: () => AppState.of(ctx).triger_login('Login'),
child: Container(
width: 200,
height: 200,
color: Colors.greenAccent,
child: Text('Page_Login'),
),
),
);
}
}
class Page_Home extends StatelessWidget {
const Page_Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext ctx) {
return Center(
child: InkWell(
onTap: () => AppState.of(ctx).triger_logout('Logout'),
child: Container(
width: 200,
height: 200,
color: Colors.blueAccent,
child: Text('Page_Home'),
),
),
);
}
}
Global loading indicator
If you want a centralized loading indicator to use in your whole app you could take advantage of Overlay's, which flutter already uses for dialogs, popups, bottom sheets etc. This way we don't introduce new widget in the widget tree.
If you only want to toggle between loading states you can use a StateProvider to handle the simple boolean value, else you could create a State/Change Notifier. This way you decouple your loading state from your AppState
final loadingProvider = StateProvider<bool>((ref) => false);
void main() => runApp(const ProviderScope(child: MaterialApp(home: GlobalLoadingIndicator(child: Home()))));
// This widget should wrap your entire app, but be below MaterialApp in order to have access to the Overlay
class GlobalLoadingIndicator extends ConsumerStatefulWidget {
final Widget child;
const GlobalLoadingIndicator({required this.child, Key? key}) : super(key: key);
#override
ConsumerState createState() => _GlobalLoadingIndicatorState();
}
class _GlobalLoadingIndicatorState extends ConsumerState<GlobalLoadingIndicator> {
//We need to cache the overlay entries we are showing as part of the indicator in order to remove them when the indicator is hidden.
final List<OverlayEntry> _entries = [];
#override
Widget build(BuildContext context) {
ref.listen<bool>(loadingProvider, (previous, next) {
// We just want to make changes if the states are different
if (previous == next) return;
if (next) {
// Add a modal barrier so the user cannot interact with the app while the loading indicator is visible
_entries.add(OverlayEntry(builder: (_) => ModalBarrier(color: Colors.black12.withOpacity(.5))));
_entries.add(OverlayEntry(
builder: (_) =>const Center(
child: Card(child: Padding(padding: EdgeInsets.all(16.0), child: CircularProgressIndicator())))));
// Insert the overlay entries into the overlay to actually show the loading indicator
Overlay.of(context)?.insertAll(_entries);
} else {
// Remove the overlay entries from the overlay to hide the loading indicator
_entries.forEach((e) => e.remove());
// Remove the cached overlay entries from the widget state
_entries.clear();
}
});
return widget.child;
}
}
We insert the GlobalLoadingIndicator high up in the widget tree although anywhere below the MaterialApp is fine (as long as it can access the Overlay via context).
The GlobalLoadingIndicator wont create extra widgets in the widget tree, and will only manage the overlays, here I add two overlays, one is a ModalBarrier which the user from interacting with widgets behind itself. And the other the actual LoadingIndicator. You are free to not add the ModalBarrier, or make it dismissible (or even if you decide to create a more complex loadingProvider, customize it in case you need to cater different use cases).
A sample usage after you have this set up is just switching the state of the loadingProvider, most of the times you would do this programatically, but for interactiveness I'll use a Switch :
class Home extends ConsumerWidget {
const Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, ref) {
final isLoading = ref.watch(loadingProvider);
return Scaffold(
appBar: AppBar(),
body: Center(
child: SwitchListTile(
value: isLoading,
onChanged: (value) {
ref.read(loadingProvider.notifier).state = value;
Future.delayed(const Duration(seconds: 4)).then((value) {
ref.read(loadingProvider.notifier).state = false;
});
},
title: const FlutterLogo(),
),
));
}
}
You can fiddle with this snippet in dartpad
Result:
Per Screen/Section loading indicator
As a side note when displaying loading states inside components of the app I recommend you to use an AnimatedSwitcher , as it fades between the widgets , super handy when dealing with screens which can change content abruptly.
final loadingProvider = StateProvider<bool>((ref) => false);
void main() => runApp(ProviderScope(child: MaterialApp(home: Home())));
class Home extends ConsumerWidget {
const Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context, ref) {
final isLoading = ref.watch(loadingProvider);
return Scaffold(
appBar: AppBar(),
body: Center(
child: SwitchListTile(
value: isLoading,
onChanged: (value) {
ref.read(loadingProvider.notifier).state = value;
},
title: AnimatedSwitcher(
duration: Duration(milliseconds: 400),
child: isLoading?CircularProgressIndicator():FlutterLogo()
),
),
));
}
}

Flutter provider profile picture not updating

I am building a method that the user can select a prefered profile picture to show arround the app, using provider package. I used shared_preferences to save the profile picture preferences on locally as a int value. And it worked, means I can save the profile picture to local system. But the problem is, the provider package completely became useless in this case, because I have to convert the widget to statefull and call the setState method when ever I insert a profilePicture widget inside the widget tree. And even the profilePicture widget in the HomeScreen not updating this way. I want to know how can I use the provider package for this issue instead of using statefulWidgets.
watch the Gif or video
This is the Provider class I created:
class ProfilePicProvider with ChangeNotifier {
ProfilePicPref profilePicPreferences = ProfilePicPref();
int _svgNumber = 1;
int get svgNumber => _svgNumber;
set svgNumber(int value) {
_svgNumber = value;
profilePicPreferences.setProfilePic(value);
notifyListeners();
}
void changePic(int val) {
_svgNumber = val;
profilePicPreferences.setProfilePic(val);
notifyListeners();
}
}
This is the sharedPreferences class
class ProfilePicPref {
static const PRO_PIC_STS = 'PROFILESTATUS';
setProfilePic(int svgNo) async {
SharedPreferences profilePref = await SharedPreferences.getInstance();
profilePref.setInt(PRO_PIC_STS, svgNo);
}
Future<int> getProfilePicture() async {
SharedPreferences profilePref = await SharedPreferences.getInstance();
return profilePref.getInt(PRO_PIC_STS) ?? 1;
}
}
This is the image selection screen and save that data to sharedPreferences class
class SelectProfilePicture extends StatefulWidget {
const SelectProfilePicture({Key? key}) : super(key: key);
#override
State<SelectProfilePicture> createState() => _SelectProfilePictureState();
}
class _SelectProfilePictureState extends State<SelectProfilePicture> {
int svgNumber = 1;
ProfilePicProvider proProvider = ProfilePicProvider();
#override
void initState() {
getCurrentProfilePicture();
super.initState();
}
void getCurrentProfilePicture() async {
proProvider.svgNumber =
await proProvider.profilePicPreferences.getProfilePicture();
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
CurrentAccountPicture(
path: 'assets/svg/${proProvider.svgNumber}.svg'),
Expanded(
child: GridView.builder(
itemCount: 15,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
),
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
setState(() {
svgNumber = index + 1;
});
proProvider.changePic(index + 1);
proProvider.svgNumber = index + 1;
},
child: SvgPicture.asset('assets/svg/${index + 1}.svg'),
);
},
),
),
],
),
);
}
}
This is the HomeScreen which is not updating the profile image whether it is statefull or stateless
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final proPicProvider = Provider.of<ProfilePicProvider>(context);
return Scaffold(
body:
Column(
children: [
Row(
children: [
CurrentAccountPicture(
path: 'assets/svg/${proPicProvider.svgNumber}.svg'),
],
),
],
),
);
}
}
example:
I have to convert the widget to statefull and call setState method to get the current profile picture from sharedPreferences. You may find this screen from the GIF I provided.
class Progress extends StatefulWidget {
const Progress({Key? key}) : super(key: key);
#override
State<Progress> createState() => _ProgressState();
}
class _ProgressState extends State<Progress> {
ProfilePicProvider proProvider = ProfilePicProvider();
#override
void initState() {
getCurrentProfilePicture();
super.initState();
}
void getCurrentProfilePicture() async {
proProvider.svgNumber =
await proProvider.profilePicPreferences.getProfilePicture();
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: SizedBox(
height: 130.0,
width: 130.0,
child: SvgPicture.asset(
'assets/svg/${proProvider.svgNumber}.svg'),
),
),
);
}
}
The problem is in _SelectProfilePictureState when you create new instance of your ChangeNotifier:
ProfilePicProvider proProvider = ProfilePicProvider();. It means you are not using the provider available across the context but creating new one every time. So when the value of your provider changed it has effect only inside _SelectProfilePictureState. Instead of creating new instance you must call it always using the context:
class SelectProfilePicture extends StatefulWidget {
const SelectProfilePicture({Key? key}) : super(key: key);
#override
State<SelectProfilePicture> createState() => _SelectProfilePictureState();
}
class _SelectProfilePictureState extends State<SelectProfilePicture> {
int svgNumber = 1;
// [removed] ProfilePicProvider proProvider = ProfilePicProvider();
//removed
/*void getCurrentProfilePicture() async {
proProvider.svgNumber =
await proProvider.profilePicPreferences.getProfilePicture();
setState(() {});
}*/
#override
Widget build(BuildContext context) {
//use provider from the context
final proProvider = Provider.of<ProfilePicProvider>(context,listen:true);
return Scaffold(
body: Column(
children: [
CurrentAccountPicture(
path: 'assets/svg/${proProvider.svgNumber}.svg'),
Expanded(
child: GridView.builder(
itemCount: 15,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
),
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
setState(() {
svgNumber = index + 1;
});
proProvider.changePic(index + 1);
proProvider.svgNumber = index + 1;
},
child: SvgPicture.asset('assets/svg/${index + 1}.svg'),
);
},
),
),
],
),
);
}
}
If you enter the application you may want send initially selected image to your provider:
Add parameter to the constructor of ProfilePicProvider:
ProfilePicProvider(SharedPreferences prefs): _svgNumber = prefs.getInt(ProfilePicPref.PRO_PIC_STS) ?? 1;
In main.dart:
Future<void> main()async{
WidgetsFlutterBinding.ensureInitialized();
var prefs = await SharedPreferences.getInstance();
runApp(
MultiProvider(
providers:[
ChangeNotifierProvider( create:(_) => ProfilePicProvider(prefs))
],
child: yourtopWidget
)
);
}

How to load Google Ads with PageView in Flutter?

I am using google_mobile_ads: ^0.12.1+1 for showing ads in my app. It's working fine in all places except in the Scaffold bottomNavigationBar which has a PageView in the body. Relevant code is -
class QuizPage extends StatefulWidget {
final List<Question> questions;
QuizPage({Key key, this.questions}) : super(key: key);
#override
_QuizPageState createState() => _QuizPageState();
}
class _QuizPageState extends State<QuizPage> {
PageController controller;
Question question;
int currentPage = 0;
#override
void initState() {
super.initState();
controller = PageController();
question = widget.questions.first;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Title'),
),
body: buildQuestionsBody(),
bottomNavigationBar: loadAds(),
);
}
Widget buildQuestionsBody() {
return PageView.builder(
onPageChanged: (index) => nextQuestion(index: index),
controller: controller,
itemCount: widget.questions.length,
itemBuilder: (context, index) {
return Container(
child: Center(
child: Text(
widget.questions[index].questionText,
textAlign: TextAlign.center,
)),
);
},
);
}
Widget loadAds() {
return Container(
height: 50,
child: AdWidget(
key: UniqueKey(),
ad: AdMobService.createBannerAd()..load(),
),
);
}
void nextQuestion({int index, bool jump = false}) {
final nextPage = controller.page + 1;
final indexPage = index ?? nextPage.toInt();
setState(() {
question = widget.questions[indexPage];
currentPage = indexPage;
});
if (jump) {
controller.jumpToPage(indexPage);
}
}
}
If I run the above code as such or shift controller = PageController(); from initState to the top where I declare the variable, the ads load initially, but on changing pages they don't load, also the app freezes without giving any error.
If I remove controller = PageController(); from initState, the ads load normally, and also on swiping pages change but now I get an error - The getter 'page' was called on null.
I am not able to find out the source of the error.
Edit 1 - After some more tries I found out that this conflict occurs whenever setState() is called for e.g. whenever I call nextQuestion in this code or other methods where setState() is called. Without google ads, setState works fine in all methods. So it seems now the question should be - How to make google ads work with setState()?
I have tried this method and it works
class QuizPage extends StatefulWidget {
final List<Question> questions;
QuizPage({Key key, #required this.questions}) : super(key: key);
#override
_QuizPageState createState() => _QuizPageState();
}
class _QuizPageState extends State<QuizPage> {
PageController _controller=PageController();
List<Widget> _questions;
#override
void initState() {
super.initState();
if(widget.questions==null)
_questions = <Widget>[Text("Alas! No questions RN")];
else{
_questions = widget.questions.map((e)=>Container(
child: Center(
child: Text(
e.questionText,
textAlign: TextAlign.center,
)),
))
.toList();
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: Appbar(
title: Text('Title'),
),
body: buildQuestionsBody(),
bottomNavigationBar: loadAds(),
);
}
Widget buildQuestionsBody() {
return PageView(
controller: _controller,
children: _questions);
}
Widget loadAds() {
return Container(
height: 50,
child: AdWidget(
key: UniqueKey(),
ad: AdMobService.createBannerAd()..load(),
),
);
}