How i pass data from showmodalbottomsheet to previous page. Below is the sample code, what i have tried is there is a button when i click it displays modalbottomsheet and when i click on Done button it should pass 1 value to previous page and also i have added setState on onTap press but still it is not changing on previous page unless i click on Flutter Hot Reload. How to solve this issue?
class TestPage extends StatefulWidget {
const TestPage({Key? key}) : super(key: key);
#override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
String textResult = "";
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(textResult),
TextButton(
onPressed: () {
showModalBottomSheet(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topRight: Radius.circular(24.0),
topLeft: Radius.circular(24.0))),
context: context,
builder: (_) => StatefulBuilder(
builder: (BuildContext context, setState) {
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () {
setState(() {
textResult = "1";
});
Navigator.pop(context);
},
child: Text(
'Done',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold),
),
),
],
),
),
],
),
);
}),
);
},
child: Text('Press'))
],
),
),
),
),
);
}
}
#Canada2000's answer is ok. I'm just extending it.
The use StatefulBuilder inside showDialog() or showModalBottomSheet()+... is these widgets are separated from current context tree.
We use StatefulBuilder when we like to show changes on dialogs. StatefulBuilder has its own state⇾ builder: (BuildContext context, setState) and calling setState is using StatefulBuilder's setstate.
Now let's say you want to change both UI, to do that you need to simply rename StatefulBuilder's state to something like SBsetState inside builder as #Canada2000 said.
to update the _TestPageState use setState((){})
to update on dialog UI, use StatefulBuilder's state like SBsetState((){})
You may can simple ignore StatefulBuilder if you don't want to show any changes on dialog.
Your widget
import 'package:flutter/material.dart';
class TestPage extends StatefulWidget {
const TestPage({Key? key}) : super(key: key);
#override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
String textResult = "";
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("text Result ${textResult}"),
TextButton(
onPressed: () {
showModalBottomSheet(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topRight: Radius.circular(24.0),
topLeft: Radius.circular(24.0))),
context: context,
builder: (_) => StatefulBuilder(
builder: (BuildContext context, setStateBTS) {
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
onTap: () {
setState(() {
textResult = "1";
});
Navigator.pop(context);
},
child: Text(
'Done',
style: TextStyle(
color: Colors.red,
fontWeight: FontWeight.bold),
),
),
],
),
),
],
),
);
}),
);
},
child: Text('Press'))
],
),
),
),
),
);
}
}
Try to change this line:
builder: (BuildContext context, setState) {
with this line
builder: (BuildContext context, StateSetter setStateModal) {
Explanation:
The outer widget in the build (Scaffold and its content) and the widget inside the showModalBottomSheet have different setState( ). You have to make sure you call the outer one when you need to update parts of that widget. The idea is to setState( ) of widgets outside your showModalBottomSheet. To be able to do that, you can give the two setState() different names so you know which one you are calling: StateSetter setStateModal
Related
I can't see what I've done wrong in the following, but it's throwing a few provider errors and buildcontext:
This happens because you used a BuildContext that does not include the provider
of your choice. There are a few common scenarios:
You added a new provider in your main.dart and performed a hot-reload.
To fix, perform a hot-restart.
The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then
other routes will not be able to access that provider.
You used a BuildContext that is an ancestor of the provider you are trying to read.
Make sure that SubscriptionsPage is under your MultiProvider/Provider.
This usually happens when you are creating a provider and trying to read it immediately.
For example, instead of:
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// Will throw a ProviderNotFoundError, because `context` is associated
// to the widget that is the parent of `Provider<Example>`
child: Text(context.watch<Example>()),
),
}
consider using builder like so:
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// we use `builder` to obtain a new `BuildContext` that has access to the provider
builder: (context) {
// No longer throws
return Text(context.watch<Example>()),
}
),
}
class RevenueCatProvider extends ChangeNotifier{
RevenueCatProvider() {
init();
}
Entitlement _entitlement = Entitlement.free;
Entitlement get entitlement => _entitlement;
Future init() async {
Purchases.addPurchaserInfoUpdateListener((purchaserInfo) async {
updatePurchasesStatus();
});
}
Future updatePurchasesStatus() async {
final purchaserInfo = await Purchases.getPurchaserInfo();
final entitlements = purchaserInfo.entitlements.active.values.toList();
_entitlement = entitlements.isEmpty ? Entitlement.free : Entitlement.pro;
notifyListeners();
}
}
class SubscriptionsPage extends StatefulWidget {
const SubscriptionsPage({Key? key}) : super(key: key);
#override
State<SubscriptionsPage> createState() => _SubscriptionsPageState();
}
class _SubscriptionsPageState extends State<SubscriptionsPage> {
bool isLoading = false;
#override
Widget build(BuildContext context) {
final entitlement = Provider.of<RevenueCatProvider>(context).entitlement;
return Scaffold(
appBar: AppBar(
title: const Text('Subscription Page'),
),
body: Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(32),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
buildEntitlement(entitlement),
const SizedBox(height: 32),
Padding(
padding: const EdgeInsets.only(left: 20.0, right: 20),
child: ElevatedButton(
style: ElevatedButton.styleFrom(
minimumSize: const Size.fromHeight(50),
),
child: const Text(
'See Available Plans',
style: TextStyle(fontSize: 20),
),
onPressed: () => isLoading ? null : fetchOffers,
),
),
const SizedBox(height: 32),
SizedBox(
height: 200,
child: Image.asset('images/logo_transparent.png'),
),
],
),
),
);
}
Widget buildEntitlement(Entitlement entitlement) {
switch (entitlement) {
case Entitlement.pro:
return Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: const [
SizedBox(height: 40),
Text('You are on a Paid plan',
style: TextStyle(
fontSize: 20,
),
),
SizedBox(height: 10),
Icon(Icons.paid,
size: 100,
),
],
);
case Entitlement.free:
default:
return Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: const [
SizedBox(height: 40),
Text('You are on a Free plan',
style: TextStyle(
fontSize: 20,
),
),
SizedBox(height: 10),
Icon(Icons.lock,
size: 100,
),
],
);
}
}
Future fetchOffers() async {
final offerings = await PurchaseApi.fetchOffers();
if (offerings.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text('No Subscription'),
));
} else {
final packages = offerings
.map((offer) => offer.availablePackages)
.expand((pair) => pair)
.toList();
showModalBottomSheet(
useRootNavigator: true,
isDismissible: true,
isScrollControlled: true,
backgroundColor: kLightPrimary,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(25.0)),
),
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setModalState) {
return PaywallWidget(
packages: packages,
title: '⭐️ Upgrade your plan',
description: 'Upgrade your plan to enjoy unlimited ad-free reviews',
onClickedPackage: (package) async {
await PurchaseApi.purchasePackage(package);
Navigator.pop(context);
},
);
});
},
);
}
}
}
You need to make sure there is a ChangeNotifierProvider somewhere in the widget tree above the widget, which uses the change notifier.
For example when you call final entitlement = Provider.of<RevenueCatProvider>(context).entitlement;. The widget tree gets traversed up in search for a matching ChangeNotifierProvider.
The error you receive tells you, there is none.
Something like this should work.
class Sample extends StatelessWidget {
const Sample({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => new RevenueCatProvider(),
child: SubscriptionsPage(),
);
}
}
I want this type of bottom menu in flutter. I scroll anywhere on screen bottom menu will appear. And when I scroll vertically down it will disappear and on slide up it will appear
For that you can use showModalBottomSheet for this type of widgets.
Sample code:
class MyStatelessWidget extends StatelessWidget {
const MyStatelessWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
child: const Text('showModalBottomSheet'),
onPressed: () {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return Container(
height: 200,
color: Colors.amber,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('Modal BottomSheet'),
ElevatedButton(
child: const Text('Close BottomSheet'),
onPressed: () => Navigator.pop(context),
)
],
),
),
);
},
);
},
),
);
}
}
Here are the good package which is useful to get pages as you want:
modal_bottom_sheet
How you can use it:
showMaterialModalBottomSheet(
context: context,
builder: (context) => Container(),
)
For more attributes, you can visit documentation here
Example Link : Modal Bottom Sheet
I'm new to flutter, and I'm trying to build an Online Shopping app as my graduation project.
Every time I run the app it goes straight the "Item Card" method and through it to "Details Screen", even though it's supposed to only go there on pressing through the Navigator widget.
It's also marking the Item Card as a dirty child (I don't quite understand what that means and how to revert it to being a normal child).
Error message: The following assertion was thrown building ItemCard(dirty):
setState() or markNeedsBuild() called during build.
I hope I explained the error well enough.. here is the code,
First is the Body class, then Item Card class, and then Details Screen class:
class Body extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.symmetric(horizontal: kDefaultPaddin),
child: Text(
"Mobiles",
style: Theme.of(context)
.textTheme
.headline5!
.copyWith(fontWeight: FontWeight.bold),
),
),
Categories(),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: kDefaultPaddin),
child: GridView.builder(
itemCount: productz.length,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: kDefaultPaddin,
crossAxisSpacing: kDefaultPaddin,
childAspectRatio: 0.75,
),
itemBuilder: (context, index) => ItemCard(
productz: productz[index],
press: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailsScreen(
productz: productz[index],
),
)),
)),
),
),
],
);
}
}
class ItemCard extends StatelessWidget {
final Productz productz;
final Function press;
const ItemCard({
Key? key,
required this.productz,
required this.press,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: press(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
child: Container(
padding: EdgeInsets.all(kDefaultPaddin),
decoration: BoxDecoration(
color: Colors.white12,
borderRadius: BorderRadius.circular(16),
),
child: Hero(
tag: "${productz.id}",
child: Image.asset(productz.item_image),
),
),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: kDefaultPaddin / 4),
child: Text(
// products is out demo list
productz.item_name,
style: TextStyle(color: kTextLightColor),
),
),
Text(
"\$${productz.item_price}",
style: TextStyle(fontWeight: FontWeight.bold),
)
],
),
);
}
}
class DetailsScreen extends StatelessWidget {
final Productz productz;
const DetailsScreen({Key? key, required this.productz}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
// each product have a color
backgroundColor: Colors.white12,
appBar: buildAppBar(context),
body: Body(productz: productz),
);
}
AppBar buildAppBar(BuildContext context) {
return AppBar(
backgroundColor: Colors.white12,
elevation: 0,
leading: IconButton(
icon: Icon(Icons.arrow_back,
size: 30,
color: Colors.white,
),
onPressed: () => Navigator.pop(context),
),
actions: <Widget>[
IconButton(
icon: Icon(FontAwesomeIcons.search),
onPressed: () {},
),
IconButton(
icon: Icon(FontAwesomeIcons.shoppingCart),
onPressed: () {},
),
SizedBox(width: kDefaultPaddin / 2)
],
);
}
}
The onPress function in the GestureDetector is being invoked immediately.
There are two ways you can go about fixing the problem.
Remove the call to the function, by removing the ()
Wrap the press function in an anonymous function, so that I can be invoked later when the user actually performs the action, this is usually done when you need to evaluate a variable, For Example in the onChanged handler of TextField or TextFormField
Check the code snippets below.
Example of Method 1.
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: press,
child: ...
)
}
Example of Method 2:
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () => press(),
child: ...
)
}
Deferreing it to the next tick will work -
Previous
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: press();
child: ...
)
}
Change to :
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: Future.delayed(Duration.zero, () async {
press();
}),
child: ...
)
}
i had the same problem...
remove the ()
Going from:
onTap: press(),
to this
onTap: press,
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!
Not sure what the issue is here but getting a basic login page setup functionally and after the page switches my sign in or signout buttons dont work (but the FAB button does). Here is my signin code for the button:
RaisedButton(
splashColor: Colors.grey,
onPressed: isLoading
? null
: () => _signInWithGoogle(context),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(40)),
highlightElevation: 0,
child: Padding(
padding: const EdgeInsets.fromLTRB(0, 10, 0, 10),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image(
Image: AssetImage("images/google_logo.png"),
height: 35.0),
Padding(
padding: const EdgeInsets.only(left: 10),
child: Text(
'Sign in with Google',
style: TextStyle(
fontSize: 20,
color: Colors.grey,
),
),
)
],
),
),
),
My main class:
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider<AuthService>(
create: (_) => AuthServiceAdapter(
initialAuthServiceType: AuthServiceType.firebase,
),
dispose: (_, AuthService authService) => authService.dispose(),
),
],
child: AuthWidgetBuilder(
builder:
(BuildContext context, AsyncSnapshot<FirebaseUser> userSnapshot) {
return MaterialApp(
home: AuthWidget(userSnapshot: userSnapshot),
routes: {
HomeScreen.id: (context) => HomeScreen(
title: 'Mythero',
),
},
debugShowCheckedModeBanner: false,
);
},
),
);
}
}
The auth widget:
class AuthWidget extends StatelessWidget {
const AuthWidget({Key key, #required this.userSnapshot}) : super(key: key);
final AsyncSnapshot<FirebaseUser> userSnapshot;
#override
Widget build(BuildContext context) {
if (userSnapshot.connectionState == ConnectionState.active) {
return userSnapshot.hasData
? HomeScreen(
title: 'Mythero',
)
: SignInPageBuilder();
}
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
}
My signout button
child: RaisedButton(
onPressed: () async {
await auth.signOut();
},
child: Text(
'Sign Out',
),
),
The login and sign out code works but when it navigates to another and then I press one of the buttons it just prints 'Dw' in the terminal output. If I refresh the page then it works just fine.