Another exception was thrown: setState() or markNeedsBuild() called during build Error in flutter - flutter

Im new to flutter and working on an ecommerce flutter app. When im trying to navigate to search screen its giving some error. Please find the below codes for your reference and help to resolve.
Error :
The following assertion was thrown while dispatching notifications for SearchProvider:
setState() or markNeedsBuild() called during build.
This _InheritedProviderScope<SearchProvider?> widget cannot be marked as needing to build because
the framework is already in the process of building widgets. A widget can be marked as needing to
be built during the build phase only if one of its ancestors is currently building. This exception
is allowed because the framework builds parent widgets before children, which means a dirty
descendant will always be built. Otherwise, the framework might not visit this widget during this
build phase.
The widget on which setState() or markNeedsBuild() was called was:
_InheritedProviderScope<SearchProvider?>
The widget which was currently being built when the offending call was made was:
SearchScreen
Codes
Search Screen
class SearchScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
Provider.of<SearchProvider>(context, listen: false).cleanSearchProduct();
Provider.of<SearchProvider>(context, listen: false).initHistoryList();
return Scaffold(
backgroundColor: ColorResources.getIconBg(context),
resizeToAvoidBottomInset: true,
body: Column(
children: [
// for tool bar
SearchWidget(
hintText: getTranslated('SEARCH_HINT', context),
onSubmit: (String text) {
Provider.of<SearchProvider>(context, listen: false)
.searchProduct(text, context);
Provider.of<SearchProvider>(context, listen: false)
.saveSearchAddress(text);
},
onClearPressed: () {
Provider.of<SearchProvider>(context, listen: false)
.cleanSearchProduct();
},
),
Consumer<SearchProvider>(
builder: (context, searchProvider, child) {
return !searchProvider.isClear
? searchProvider.searchProductList != null
? searchProvider.searchProductList.length > 0
? Expanded(
child: SearchProductWidget(
products: searchProvider.searchProductList,
isViewScrollable: true))
: Expanded(
child:
NoInternetOrDataScreen(isNoInternet: false))
: Expanded(
child: ProductShimmer(
isHomePage: false,
isEnabled: Provider.of<SearchProvider>(context)
.searchProductList ==
null))
: Expanded(
flex: 4,
child: Container(
padding:
EdgeInsets.all(Dimensions.PADDING_SIZE_DEFAULT),
child: Stack(
clipBehavior: Clip.none,
children: [
Consumer<SearchProvider>(
builder: (context, searchProvider, child) =>
StaggeredGridView.countBuilder(
crossAxisCount: 3,
physics: NeverScrollableScrollPhysics(),
itemCount: searchProvider.historyList.length,
itemBuilder: (context, index) => Container(
alignment: Alignment.center,
child: InkWell(
onTap: () {
Provider.of<SearchProvider>(context,
listen: false)
.searchProduct(
searchProvider
.historyList[index],
context);
},
borderRadius: BorderRadius.circular(20),
child: Container(
padding: EdgeInsets.only(
left: 10,
right: 10,
top: 2,
bottom: 2),
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(16),
color: ColorResources.getGrey(
context)),
width: double.infinity,
child: Center(
child: Text(
Provider.of<SearchProvider>(context,
listen: false)
.historyList[index] ??
"",
style: titilliumItalic.copyWith(
fontSize:
Dimensions.FONT_SIZE_SMALL),
),
),
),
)),
staggeredTileBuilder: (int index) =>
new StaggeredTile.fit(1),
mainAxisSpacing: 4.0,
crossAxisSpacing: 4.0,
),
),
Positioned(
top: -5,
left: 0,
right: 0,
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(getTranslated('SEARCH_HISTORY', context),
style: robotoBold),
InkWell(
borderRadius: BorderRadius.circular(10),
onTap: () {
Provider.of<SearchProvider>(context,
listen: false)
.clearSearchAddress();
},
child: Container(
padding: EdgeInsets.all(5),
child: Text(
getTranslated('REMOVE', context),
style: titilliumRegular.copyWith(
fontSize:
Dimensions.FONT_SIZE_SMALL,
color: Theme.of(context)
.primaryColor),
)))
],
),
),
],
),
),
);
},
),
],
),
);
}
}
Providers
void initHistoryList() {
_historyList = [];
_historyList.addAll(searchRepo.getSearchAddress());
notifyListeners();
}
void cleanSearchProduct() {
_searchProductList = [];
_isClear = true;
_searchText = '';
notifyListeners();
}

Try to use initial function calling in initState instead of build function
#override
void initState() {
WidgetsBinding.instance!.addPostFrameCallback((_) {
Provider.of<SearchProvider>(context, listen: false).cleanSearchProduct();
Provider.of<SearchProvider>(context, listen: false).initHistoryList();
});
super.initState();
}

Related

How to create multi select CheckboxListTile perfectly in flutter?

I have a Bottom sheet which shows the list of Cities from which I have to select multiple Cities and if I open the bottomSheet second time previous selected cities should be pre selected but only if user press the save button after selecting Cities and if bottomSheet is dismissed then dont include the new selected cities. I am able to select the multiple cities and show previous selected cities in second time. also I am getting null as returned value if bottomSheet is dismissed. but also I am getting upated data in case of dismiss I dont know how it is gettting initialized with new selected values.
below is my code.
This is my bottom sheet.
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../../../../business_logic/app/app_event_and_state.dart';
import '../bloc/city_bloc.dart';
import '../bloc/city_event.dart';
import '../bloc/city_state.dart';
import '../model/city_model.dart';
class MultiSelectCity extends StatefulWidget {
const MultiSelectCity({Key? key, this.selectedCitiesList}) : super(key: key);
final List<City>? selectedCitiesList;
#override
State<MultiSelectCity> createState() => _MultiSelectCityState();
}
class _MultiSelectCityState extends State<MultiSelectCity> {
bool _isInit = true;
List<City>? selectedCities = [];
List<City> filteredCities = [];
#override
void didChangeDependencies() {
print('didChangeDependencies');
if (_isInit) {
BlocProvider.of<CityBloc>(context)
.add(const AppEventLoadCitiesWithFilter(isActive: true));
}
_isInit = false;
selectedCities = widget.selectedCitiesList;
super.didChangeDependencies();
}
#override
Widget build(BuildContext context) {
return BlocConsumer<CityBloc, AppState>(
builder: (context, state) {
return DraggableScrollableSheet(
initialChildSize: 0.6,
minChildSize: 0.4,
maxChildSize: 0.75,
expand: false,
builder: (context, scrollController) => Container(
color: Colors.white,
child: Column(children: [
Container(
color: Theme.of(context).colorScheme.background,
padding: EdgeInsets.symmetric(vertical: 25.h, horizontal: 45.w),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
height: 15.h,
width: 140.w,
decoration: BoxDecoration(
shape: BoxShape.rectangle,
borderRadius: BorderRadius.circular(25.r),
color: Colors.grey.shade400,
),
),
Padding(
padding:
EdgeInsets.only(top: 20.h, left: 10.w, bottom: 2.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 25.h),
Text('Select City',
style: TextStyle(
fontWeight: FontWeight.w900,
fontSize: 65.sp)),
],
),
),
],
),
),
Expanded(
child: state.isLoading
? const Center(
child: CircularProgressIndicator(),
)
: filteredCities.isNotEmpty
? ListView.builder(
controller: scrollController,
itemBuilder: (context, index) {
return CheckboxListTile(
title: Text(
'${filteredCities[index].cityName},'
' ${filteredCities[index].stateCode}, '
'${filteredCities[index].countryName}',
style:
Theme.of(context).textTheme.bodyLarge,
),
value: selectedCities?.contains(
filteredCities[index]) ,
onChanged: (bool? value) {
setState(() {
if (selectedCities?.contains(
filteredCities[index]) ==
true) {
selectedCities?.removeWhere((element) =>
element ==
filteredCities[index]);
} else {
selectedCities
?.add(filteredCities[index]);
}
print(value);
});
},
);
},
itemCount: filteredCities.length,
)
: const Center(
child: Text("No cities found"),
),),
Container(
width: double.infinity,
height: 50,
margin: EdgeInsets.all(20),
child: ElevatedButton(
onPressed: () {
return Navigator.of(context).pop(selectedCities); // if save pressed then pass selected cities else if bottomsheet is dismissed it returns null.
},
child: Text(
'Save',
style: TextStyle(
fontWeight: FontWeight.w700, fontSize: 45.sp),
)),
)
]),
),
);
},
listener: (context, state) {
if (state is AppStateActiveCities) {
print(state.activeCities.length.toString());
filteredCities = state.activeCities;
}
},
);
}
}
This is how I am calling it.
Future<void> _showMultiSelectCityOptions() async {
returnedSelectedCities = await showModalBottomSheet<List<City>>(
isScrollControlled: true,
isDismissible: false,
context: context,
builder: (context) => Padding(
padding:
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: MultiSelectCity(selectedCitiesList: selectedCities),
),
);
// here i am getting null as expected if bottomSheet is dismissed but also seletedCites are getting updated I dont know how. can some explain it.
if (returnedSelectedCities != null) {
setState(() {
selectedCities = returnedSelectedCities;
});
}
}

How to wait for a request to complete using ObservableFuture?

When I transition to a screen where I get a list of information via an API, it initially gives an error:
_CastError (Null check operator used on a null value)
and only after loading the information, the screen is displayed correctly.
I am declaring the variables like this:
#observable
ObservableFuture<Model?>? myKeys;
#action
getKeys() {
myKeys = repository.getKeys().asObservable();
}
How can I enter the page only after loading the information?
In button action I tried this but to no avail!
await Future.wait([controller.controller.getKeys()]);
Modular.to.pushNamed('/home');
This is the page where the error occurs momentarily, but a short time later, that is, when the api call occurs, the data appears on the screen.
class MyKeyPage extends StatefulWidget {
const MyKeyPage({Key? key}) : super(key: key);
#override
State<MyKeyPage> createState() => _MyKeyPageState();
}
class _MyKeyPageState
extends ModularState<MyKeyPage, KeyController> {
KeyController controller = Modular.get<KeyController>();
Widget countKeys() {
return FutureBuilder(
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
final count =
controller.myKeys?.value?.data!.length.toString();
if (snapshot.connectionState == ConnectionState.none &&
!snapshot.hasData) {
return Text('..');
}
return ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: 1,
itemBuilder: (context, index) {
return Text(count.toString() + '/5');
});
},
future: controller.getCountKeys(),
);
}
#override
Widget build(BuildContext context) {
Size _size = MediaQuery.of(context).size;
return controller.getCountKeys() != "0"
? TesteScaffold(
removeHorizontalPadding: true,
onBackPressed: () => Modular.to.navigate('/exit'),
leadingIcon: ConstantsIcons.trn_arrow_left,
title: '',
child: Container(
width: double.infinity,
child: Padding(
padding: const EdgeInsets.only(left: 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Keys',
style: kHeaderH3Bold.copyWith(
color: kBluePrimaryTrinus,
),
),
countKeys(),
],
),
),
),
body: Observer(builder: (_) {
return Padding(
padding: const EdgeInsets.only(bottom: 81),
child: Container(
child: ListView.builder(
padding: EdgeInsets.only(
left: 12.0,
top: 2.0,
right: 12.0,
),
itemCount:
controller.myKeys?.value?.data!.length,
itemBuilder: (context, index) {
var typeKey = controller
.myKeys?.value?.data?[index].type
.toString();
var id =
controller.myKeys?.value?.data?[index].id;
final value = controller
.myKeys?.value?.data?[index].value
.toString();
return GestureDetector(
onTap: () {
.
.
},
child: CardMeyKeys(
typeKey: typeKey,
value: value!.length > 25
? value.substring(0, 25) + '...'
: value,
myKeys: pixController
.minhasChaves?.value?.data?[index].type
.toString(),
),
);
},
),
),
);
}),
bottomSheet: ....
)
: TesteScaffold(
removeHorizontalPadding: true,
onBackPressed: () => Modular.to.navigate('/exit'),
leadingIcon: ConstantsIcons.trn_arrow_left,
title: '',
child: Container(
width: double.infinity,
child: Padding(
padding: const EdgeInsets.only(left: 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'...',
style: kHeaderH3Bold.copyWith(
color: kBluePrimaryTrinus,
),
),
],
),
),
),
body: Padding(
padding: const EdgeInsets.only(bottom: 81),
child: Container(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.asset(
'assets/images/Box.png',
fit: BoxFit.cover,
width: 82.75,
height: 80.91,
),
SizedBox(
height: 10,
),
],
),
), //Center
),
),
bottomSheet: ...
);
}
List<ReactionDisposer> disposers = [];
#override
void initState() {
super.initState();
controller.getKeys();
}
#override
void dispose() {
disposers.forEach((toDispose) => toDispose());
super.dispose();
}
}
Initially the error occurs in this block
value: value!.length > 25
? value.substring(0, 25) + '...'
: value,
_CastError (Null check operator used on a null value)
I appreciate if anyone can help me handle ObservableFuture correctly!
You need to call the "future" adding
Future.wait
(the return type of getKeys) keys=await Future.wait([
controller.getKeys();
]);
The problem is your getKeys function isn't returning anything, so there's nothing for your code to await. You need to return a future in order to await it.
Future<Model?> getKeys() {
myKeys = repository.getKeys().asObservable();
return myKeys!; // Presumably this isn't null anymore by this point.
}
...
await controller.controller.getKeys();
Modular.to.pushNamed('/home');

LateError was thrown building Obx(has builder, dirty, state: _ObxState#17527): LateInitializationError: Field 'products' has not been initialized

I am using GetX. I need to display the data from database one the application open. But i get the Exception.
The following LateError was thrown building Obx(has builder, dirty,
state: _ObxState#17527): LateInitializationError: Field 'products' has
not been initialized.
Although, I have initialize the late variable inside onInit.
The Controller Code Is:
class HomeController extends GetxController {
HomeController({required this.homeRepository});
final IHomeRepository homeRepository;
late Rx<Either<FireStoreServerFailures, List<Product>>> products; //=
#override
void onInit() async {
products.value= await fetchProductsFromDB();
super.onInit();
}
Future<Either<FireStoreServerFailures, List<Product>>> fetchProductsFromDB() async {
var _completer=Completer<Either<FireStoreServerFailures, List<Product>>>();
homeRepository.fetchProducts().listen((event) {
_completer.complete(event);
});
return await _completer.future;
}
}
Home Page Where I Am Used The Late Variable's Code Is:
Obx(
() => GridView.builder(
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
childAspectRatio: 2 / 2,
),
// TODO Deal with errors
itemCount: controller.products.value.getOrElse(() => []).length,
itemBuilder: (context, index) {
return controller.products.value.fold(
(l) => Container(
height: double.infinity,
width: double.infinity,
color: Colors.red,
child: Text("Error ${l.msg}"),
),
(r) => Card(
elevation: 2.0,
color: Colors.amber,
child: InkWell(
onTap: () async =>
await controller.goToMoreDetails(),
child: Stack(
fit: StackFit.expand,
clipBehavior: Clip.hardEdge,
children: [
Image.network(
controller.products.value.fold(
(l) => "",
(r) => r[index]
.pickedImages
.getOrCrash()[0]),
fit: BoxFit.fill,
height: 200,
width: 200,
),
OverflowBox(
minHeight: 30,
alignment: Alignment.bottomCenter,
child: Container(
color: Colors.black.withOpacity(0.7),
height: 30,
width: double.infinity,
child: Center(
child: Text(
"${controller.products.value.fold((l) => "", (r) => r[index].price.getOrCrash())}",
style: const TextStyle(
color: Colors.white,
),
),
),
),
),
],
),
),
)) ;
}),
);
You declared late Rx<Either<FireStoreServerFailures, List<Product>>> products; but did not initialise products anywhere. But on your onInit method you tried to access .value on products which is not yet initialised. Setting products.value is not initialising products. Also, your Obx widget tries to access the products field which is not initialised by the time it tries to access.
What you should do is:
final products = Rxn<Either<FireStoreServerFailures, List<Product>>>();
And in your UI:
Obx(
() => controller.products.value == null? CircularProgressIndicator() : GridView.builder(......

Return variable from current screen to previous screen

So I am implementing a 'settings' view in my Flutter app. The idea is all settings will appear in a ListView, and when the user will click on a ListTile, a showModalBottomSheet will pop where the user will be able to manipulate the setting. The only problem I am having is I am unable to migrate the showModalBottomSheet to a separate class as I cannot make the new function (outside the class) return the manipulated setting variable. This has lead to a messy code, all in a single class.
class Page extends StatefulWidget {
Page({Key key}) : super(key: key);
#override
_Page createState() => _Page();
}
class _Page extends State<Page> {
var value;
#override
Widget build(BuildContext context) {
return ListView(
children: <Widget>[
ListTile(
title: Text("Age"),
trailing: Text(value),
onTap: () {
setState(() {
value = _valueSelector(); // This doesn't work, but to give an idea what I want
});
},
),
],
);
}
}
int _valueSelector(context) { // Doesn't return
var age = 0;
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Wrap(
children: [
Column(
children: <Widget>[
Slider(
value: age.toDouble(),
min: 0,
max: 18,
divisions: 18,
onChanged: (value) {
setState(() {
age = value.toInt();
});
},
),
],
),
],
);
});
},
).whenComplete(() {
return age; // Not sure if return is supposed to be here
});
}
How can I implement showModalBottomSheet in a separate class and just make it return the variable representing the setting chosen by the user?
You can try the below code,
First, create a class custom_bottom_sheet.dart and add the below code. You can use it everywhere in the project. And also use this library modal_bottom_sheet: ^0.2.0+1 to get the showMaterialModalBottomSheet.
customBottomSheet(BuildContext context, {#required Widget widget}) async {
return await showMaterialModalBottomSheet(
context: context,
backgroundColor: AppColors.transparent_100,
barrierColor: AppColors.black_75,
isDismissible: false,
enableDrag: true,
builder: (_, ScrollController scrollController) {
return widget;
},
);
}
Sample example code:
Create another class called bottom_sheet_example.dart and add the below code.
class BottomSheetExample {
static Future getSheet(BuildContext _context,
{ValueChanged<bool> onChanged}) async {
await customBottomSheet(
_context,
widget: SafeArea(
child: Container(
padding: EdgeInsets.only(left: 40.0, right: 40.0),
height: 170.0,
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(27.0),
topRight: Radius.circular(27.0))),
child: Container(
padding: EdgeInsets.only(top: 32),
child: Column(
children: [
Text("Were you at Queen Victoria Building?"),
SizedBox(height: 48),
Row(
children: [
Expanded(
child: RaisedButton(
child: Text("No"),
onPressed: () {
Navigator.of(_context).pop();
onChanged(false);
},
),
),
SizedBox(width: 18),
Expanded(
child: RaisedButton(
child: Text("Yes"),
onPressed: () {
Navigator.of(_context).pop();
onChanged(true);
},
),
),
],
),
SizedBox(height: 24),
],
),
),
)),
);
}
}
Button click to show the bottom sheet
#override
Widget build(BuildContext context) {
return Scaffold(
body: yourBodyWidget(),
bottomNavigationBar: Container(
height: 40,
width: double.infinity,
child: FlatButton(
onPressed: () {
/// call BottomSheetExample class
BottomSheetExample.getSheet(
context,
onChanged: (bool result) async {
///
/// add your code
},
);
},
child: Text("show bottom sheet")),
),
);
}
In onChanged callback you can return your value(obj/String/num/bool/list).
Thank you!

Passing value to previous widget

I have simple form , inside it have CircularAvatar when this is pressed show ModalBottomSheet to choose between take picture from gallery or camera. To make my widget more compact , i separated it to some file.
FormDosenScreen (It's main screen)
DosenImagePicker (It's only CircularAvatar)
ModalBottomSheetPickImage (It's to show ModalBottomSheet)
The problem is , i don't know how to passing value from ModalBottomSheetPickImage to FormDosenScreen. Because value from ModalBottomSheetPickImage i will use to insert operation.
I only success passing from third Widget to second Widget , but when i passing again from second Widget to first widget the value is null, and i think the problem is passing from Second widget to first widget.
How can i passing from third Widget to first Widget ?
First Widget
class FormDosenScreen extends StatefulWidget {
static const routeNamed = '/formdosen-screen';
#override
_FormDosenScreenState createState() => _FormDosenScreenState();
}
class _FormDosenScreenState extends State<FormDosenScreen> {
String selectedFile;
#override
Widget build(BuildContext context) {
final detectKeyboardOpen = MediaQuery.of(context).viewInsets.bottom;
print('trigger');
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('Tambah Dosen'),
actions: <Widget>[
PopupMenuButton(
itemBuilder: (_) => [
PopupMenuItem(
child: Text('Tambah Pelajaran'),
value: 'add_pelajaran',
),
],
onSelected: (String value) {
switch (value) {
case 'add_pelajaran':
Navigator.of(context).pushNamed(FormPelajaranScreen.routeNamed);
break;
default:
}
},
)
],
),
body: Stack(
fit: StackFit.expand,
children: <Widget>[
SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
SizedBox(height: 20),
DosenImagePicker(onPickedImage: (file) => selectedFile = file),
SizedBox(height: 20),
Card(
margin: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
TextFormFieldCustom(
onSaved: (value) {},
labelText: 'Nama Dosen',
),
SizedBox(height: 20),
TextFormFieldCustom(
onSaved: (value) {},
prefixIcon: Icon(Icons.email),
labelText: 'Email Dosen',
keyboardType: TextInputType.emailAddress,
),
SizedBox(height: 20),
TextFormFieldCustom(
onSaved: (value) {},
keyboardType: TextInputType.number,
inputFormatter: [
// InputNumberFormat(),
WhitelistingTextInputFormatter.digitsOnly
],
prefixIcon: Icon(Icons.local_phone),
labelText: 'Telepon Dosen',
),
],
),
),
),
SizedBox(height: kToolbarHeight),
],
),
),
Positioned(
child: Visibility(
visible: detectKeyboardOpen > 0 ? false : true,
child: RaisedButton(
onPressed: () {
print(selectedFile);
},
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
color: colorPallete.primaryColor,
child: Text(
'SIMPAN',
style: TextStyle(fontWeight: FontWeight.bold, fontFamily: AppConfig.headerFont),
),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
textTheme: ButtonTextTheme.primary,
),
),
bottom: kToolbarHeight / 2,
left: sizes.width(context) / 15,
right: sizes.width(context) / 15,
)
],
),
);
}
}
Second Widget
class DosenImagePicker extends StatefulWidget {
final Function(String file) onPickedImage;
DosenImagePicker({#required this.onPickedImage});
#override
DosenImagePickerState createState() => DosenImagePickerState();
}
class DosenImagePickerState extends State<DosenImagePicker> {
String selectedImage;
#override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.center,
child: InkWell(
onTap: () async {
await showModalBottomSheet(
context: context,
builder: (context) => ModalBottomSheetPickImage(
onPickedImage: (file) {
setState(() {
selectedImage = file;
widget.onPickedImage(selectedImage);
print('Hellooo dosen image picker $selectedImage');
});
},
),
);
},
child: CircleAvatar(
foregroundColor: colorPallete.black,
backgroundImage: selectedImage == null ? null : MemoryImage(base64.decode(selectedImage)),
radius: sizes.width(context) / 6,
backgroundColor: colorPallete.accentColor,
child: selectedImage == null ? Text('Pilih Gambar') : SizedBox(),
),
),
);
}
}
Third Widget
class ModalBottomSheetPickImage extends StatelessWidget {
final Function(String file) onPickedImage;
ModalBottomSheetPickImage({#required this.onPickedImage});
#override
Widget build(BuildContext context) {
return SizedBox(
child: Padding(
padding: const EdgeInsets.all(15.0),
child: Wrap(
alignment: WrapAlignment.spaceEvenly,
children: <Widget>[
InkWell(
onTap: () async {
final String resultBase64 =
await commonFunction.pickImage(quality: 80, returnFile: ReturnFile.BASE64);
onPickedImage(resultBase64);
},
child: CircleAvatar(
foregroundColor: colorPallete.white,
backgroundColor: colorPallete.green,
child: Icon(Icons.camera_alt),
),
),
InkWell(
onTap: () async {
final String resultBase64 =
await commonFunction.pickImage(returnFile: ReturnFile.BASE64, isCamera: false);
onPickedImage(resultBase64);
},
child: CircleAvatar(
foregroundColor: colorPallete.white,
backgroundColor: colorPallete.blue,
child: Icon(Icons.photo_library),
),
),
],
),
),
);
}
}
The cleanest and easiest way to do this is through Provider. It is one of the state management solutions you can use to pass values around the app as well as rebuild only the widgets that changed. (Ex: When the value of the Text widget changes). Here is how you can use Provider in your scenario:
This is how your model should look like:
class ImageModel extends ChangeNotifier {
String _base64Image;
get base64Image => _base64Image;
set base64Image(String base64Image) {
_base64Image = base64Image;
notifyListeners();
}
}
Don't forget to add getters and setters so that you can use notifyListeners() if you have any ui that depends on it.
Here is how you can access the values of ImageModel in your UI:
final model=Provider.of<ImageModel>(context,listen:false);
String image=model.base64Image; //get data
model.base64Image=resultBase64; //set your image data after you used ImagePicker
Here is how you can display your data in a Text Widget (Ideally, you should use Selector instead of Consumer so that the widget only rebuilds if the value its listening to changes):
#override
Widget build(BuildContext context) {
//other widgets
Selector<ImageModel, String>(
selector: (_, model) => model.base64Image,
builder: (_, image, __) {
return Text(image);
},
);
}
)
}
You could achieve this easily. If you are using Blocs.