Flutter implementing custom BottomSheet - flutter

Like with awesome bottom sheet of Telegram i would like to have that in our application, i tired to implementing that, but unfortunately i don't have more experience about that and i can implement below code like with that which it doesn't have more feature.
here i attached some Gif images such as what features i want to have them on our application:
opening animation:
switch between tabs animation:
expand and collapsing animation:
preventing closing bottom sheet during dragging down:
Full code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool isLong = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Sample'),
),
body: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextButton(
onPressed: _onPressed,
child: Text('open'),
)
],
),
),
);
}
_onPressed() {
setImages();
Navigator.of(context).push(TransparentRoute(builder: (context) => NewWidget(images)));
}
List<String> images =[];
void setImages() {
images = List.generate(
isLong ? 5 : 25,
(_) => 'http://placeimg.com/100/100/any',
);
}
}
class NewWidget extends StatefulWidget {
const NewWidget(this.images, {Key? key}) : super(key: key);
final List<String> images;
#override
_NewWidgetState createState() => _NewWidgetState();
}
class _NewWidgetState extends State<NewWidget> {
bool isBig = false;
bool isBouncing = true;
final double topOffset = 200;
final double miniHandleHeight = 20;
double safeAreaPadding = 0;
double? startBigAnimationOffset;
double? startStickyOffset;
double backgroundHeight = 0;
double get savedAppBarHeight => safeAreaPadding + kToolbarHeight;
final ScrollController controller = ScrollController();
#override
void initState() {
WidgetsBinding.instance!.addPostFrameCallback(_afterLayout);
super.initState();
}
void _afterLayout(_) {
var media = MediaQuery.of(context);
safeAreaPadding = media.padding.top;
startBigAnimationOffset = topOffset - savedAppBarHeight;
startStickyOffset = (startBigAnimationOffset! + 10);
backgroundHeight = media.size.height - miniHandleHeight - topOffset;
controller.addListener(scrollListener);
}
void scrollListener() {
var offset = controller.offset;
if (offset < 0) {
goOut();
} else if (offset < startBigAnimationOffset! && isBig || offset < startStickyOffset!) {
setState(() {
isBig = false;
});
} else if (offset > startBigAnimationOffset! && !isBig || offset > startStickyOffset!) {
setState(() {
isBig = true;
});
}
if (offset < topOffset && !isBouncing) {
setState(() => isBouncing = true);
} else if (offset > topOffset && isBouncing) {
setState(() => isBouncing = false);
}
}
void goOut() {
controller.dispose();
Navigator.of(context).pop();
}
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
backgroundColor: /*isStack ? Colors.white : */Colors.transparent,
body: Stack(
children: [
ListView(
padding: EdgeInsets.zero,
physics: isBouncing ? BouncingScrollPhysics() : ClampingScrollPhysics(),
controller: controller,
children: <Widget>[
Container(
alignment: Alignment.bottomCenter,
height: topOffset,
child: TweenAnimationBuilder(
tween: Tween(begin: 0.0, end: isBig ? 1.0 : 0.0),
duration: Duration(milliseconds: 200),
child: Align(
alignment: Alignment.topCenter,
child: Padding(
padding: EdgeInsets.only(top: 10),
child: Container(
height: 5,
width: 60,
),
),
),
builder: (_, number, child) {
return Container(
height: savedAppBarHeight * (number as double) + miniHandleHeight,
decoration: BoxDecoration(
borderRadius: BorderRadius.vertical(
top: Radius.circular((1 - number) * 50),
),
color: Colors.white,
),
child: Opacity(opacity: 1 - (number), child: child),
);
}),
),
Container(
padding: EdgeInsets.all(5),
constraints: BoxConstraints(
minHeight: MediaQuery.of(context).size.height - savedAppBarHeight,
),
decoration: BoxDecoration(
color: Colors.white,
),
child: getGrid(),
)
],
)
],
),
),
);
}
Widget getGrid() {
return GridView.count(
crossAxisSpacing: 10,
mainAxisSpacing: 10,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
crossAxisCount: 3,
children: widget.images.map((url) {
return Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.blueAccent,
),
),
child: Image(
image: NetworkImage(url),
),
);
}).toList(),
);
}
}
class TransparentRoute extends PageRoute<void> {
TransparentRoute({
required this.builder,
RouteSettings? settings,
}) : super(settings: settings, fullscreenDialog: false);
final WidgetBuilder builder;
#override
bool get opaque => false;
#override
Color? get barrierColor => null;
#override
String? get barrierLabel => null;
#override
bool get maintainState => true;
#override
Duration get transitionDuration => Duration(milliseconds: 350);
#override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
final result = builder(context);
return Container(
color: Colors.black.withOpacity(0.5),
child: SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 1),
end: Offset.zero,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeIn,
)),
child: result,
),
);
}
}

Here is the outline, with which you can try to implement it. If you feel difficulty, feel free to ask for more suggestions. IMHO, this is not very challenging to implement (but of course need some time to adjust the fine details into what you want).
Firstly, you mentioned animations many times. Have a look at the official guide here: https://flutter.dev/docs/development/ui/animation. For example, if we want to implement the "opening animation" and the "switch between tabs animation" (and other similar things), we want a Container (or other widgets) to have a size changing with time as the animation goes. There are many ways to implement this, as is suggested in the link provided above. For example, we may use AnimatedContainer directly - it even has a video explaining this here. You can also use explicit animations or other things.
Secondly, as for the animating icons, you may use Lottie https://lottiefiles.com/ to create whatever complex animation as you like. Its flutter plugin is here: https://pub.dev/packages/lottie.
Thirdly, as for "preventing closing bottom sheet during dragging down", I will suggest the following: ListView(children: [Container(height:300,color:Colors.black), ...your_real_children...]). Then when dragging down it will not be closed, but only show a header of at most 300 height.
Lastly, you are not restricted to use showModalBottomSheet or things like that. Indeed, when using flutter, you are completely free. So you can just do Navigator.push and draw whatever widget you like without the constraints that modal bottom sheet gives you. Personally, I suggest that you first start with showModalBottomSheet, then when it does not satisfy your needs, you just directly copy its source code and do whatever modifications as you needed. In short, use something in Flutter, and when you need more control, copy the source code and modify it.

You can be helped from this link https://pub.dev/packages/modal_bottom_sheet
import 'package:example/modals/circular_modal.dart';
import 'package:example/web_frame.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'modals/floating_modal.dart';
import 'modals/modal_complex_all.dart';
import 'modals/modal_fit.dart';
import 'modals/modal_inside_modal.dart';
import 'modals/modal_will_scope.dart';
import 'modals/modal_with_navigator.dart';
import 'modals/modal_with_nested_scroll.dart';
import 'modals/modal_with_scroll.dart';
import 'modals/modal_with_page_view.dart';
import 'examples/cupertino_share.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(platform: TargetPlatform.iOS),
title: 'BottomSheet Modals',
builder: (context, Widget? child) => WebFrame(
child: child!,
),
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case '/':
return MaterialWithModalsPageRoute(
builder: (_) => MyHomePage(title: 'Flutter Demo Home Page'),
settings: settings);
}
return MaterialPageRoute(
builder: (context) => Scaffold(
body: CupertinoScaffold(
body: Builder(
builder: (context) => CupertinoPageScaffold(
backgroundColor: Colors.white,
navigationBar: CupertinoNavigationBar(
transitionBetweenRoutes: false,
middle: Text('Normal Navigation Presentation'),
trailing: GestureDetector(
child: Icon(Icons.arrow_upward),
onTap: () => CupertinoScaffold
.showCupertinoModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context) => Stack(
children: <Widget>[
ModalWithScroll(),
Positioned(
height: 40,
left: 40,
right: 40,
bottom: 20,
child: MaterialButton(
onPressed: () => Navigator.of(
context)
.popUntil((route) =>
route.settings.name == '/'),
child: Text('Pop back home'),
),
)
],
),
)),
),
child: Center(child: Container()),
),
),
),
),
settings: settings);
},
debugShowCheckedModeBanner: false,
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, required this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Material(
child: Scaffold(
body: CupertinoPageScaffold(
backgroundColor: Colors.white,
navigationBar: CupertinoNavigationBar(
transitionBetweenRoutes: false,
middle: Text('iOS13 Modal Presentation'),
trailing: GestureDetector(
child: Icon(Icons.arrow_forward),
onTap: () => Navigator.of(context).pushNamed('ss'),
),
),
child: SizedBox.expand(
child: SingleChildScrollView(
primary: true,
child: SafeArea(
bottom: false,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
title: Text('Cupertino Photo Share Example'),
onTap: () => Navigator.of(context).push(
MaterialWithModalsPageRoute(
builder: (context) => CupertinoSharePage()))),
section('STYLES'),
ListTile(
title: Text('Material fit'),
onTap: () => showMaterialModalBottomSheet(
expand: false,
context: context,
backgroundColor: Colors.transparent,
builder: (context) => ModalFit(),
)),
ListTile(
title: Text('Bar Modal'),
onTap: () => showBarModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context) => ModalInsideModal(),
)),
ListTile(
title: Text('Avatar Modal'),
onTap: () => showAvatarModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context) => ModalInsideModal(),
)),
ListTile(
title: Text('Float Modal'),
onTap: () => showFloatingModalBottomSheet(
context: context,
builder: (context) => ModalFit(),
)),
ListTile(
title: Text('Cupertino Modal fit'),
onTap: () => showCupertinoModalBottomSheet(
expand: false,
context: context,
backgroundColor: Colors.transparent,
builder: (context) => ModalFit(),
)),
section('COMPLEX CASES'),
ListTile(
title: Text('Cupertino Small Modal forced to expand'),
onTap: () => showCupertinoModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context) => ModalFit(),
)),
ListTile(
title: Text('Reverse list'),
onTap: () => showBarModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context) =>
ModalInsideModal(reverse: true),
)),
ListTile(
title: Text('Cupertino Modal inside modal'),
onTap: () => showCupertinoModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context) => ModalInsideModal(),
)),
ListTile(
title: Text('Cupertino Modal with inside navigation'),
onTap: () => showCupertinoModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context) => ModalWithNavigator(),
)),
ListTile(
title:
Text('Cupertino Navigator + Scroll + WillPopScope'),
onTap: () => showCupertinoModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context) => ComplexModal(),
)),
ListTile(
title: Text('Modal with WillPopScope'),
onTap: () => showCupertinoModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.transparent,
builder: (context) => ModalWillScope(),
)),
ListTile(
title: Text('Modal with Nested Scroll'),
onTap: () => showCupertinoModalBottomSheet(
expand: true,
context: context,
builder: (context) => NestedScrollModal(),
)),
ListTile(
title: Text('Modal with PageView'),
onTap: () => showBarModalBottomSheet(
expand: true,
context: context,
builder: (context) => ModalWithPageView(),
)),
SizedBox(
height: 60,
)
],
),
),
),
),
),
),
);
}
Widget section(String title) {
return Padding(
padding: EdgeInsets.fromLTRB(16, 20, 16, 8),
child: Text(
title,
style: Theme.of(context).textTheme.caption,
));
}
}
another helpful link [https://pub.dev/packages/draggable_bottom_sheet][2]

Related

Flutter transition like iOS 13 modal full screen

I would like to have the iOS-Modal-Transition where the new screen animates from the bottom and the old screen is being pushed behind. I found this very promising package:
modal_bottom_sheet
This is the function I am using to show the modal:
showCupertinoModalBottomSheet(
expand: true,
context: context,
builder: (context) => Container(
color: AppColors.blue,
),
);
However this is not working a 100% correctly as the view behind is not being pushed in the back.
What am I missing here? Let me know if anything is unclear!
Here is some more of my code:
This is my whole page, from where I would like to have the transition:
class _MonthPageState extends State<MonthPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColors.secondary,
body: SafeArea(
child: Stack(
children: [
...
Positioned(
bottom: 10,
right: 20,
child: Hero(
tag: widget.month.name + 'icon',
child: AddButton(
onTapped: () {
showCupertinoModalBottomSheet(
expand: true,
context: context,
builder: (context) => Container(
color: AppColors.blue,
),
);
},
),
),
),
],
),
),
);
}
And this is my Router:
class AppRouter {
static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) {
case '/':
return MaterialWithModalsPageRoute(
builder: (context) => HomePage(),
);
case '/month':
final Month month = settings.arguments as Month;
return _buildTransitionToMonthPage(month);
default:
return MaterialPageRoute(
builder: (_) => Scaffold(
body: Center(
child: Text('No route defined for ${settings.name}'),
),
),
);
}
}
static PageRouteBuilder _buildTransitionToMonthPage(Month month) {
return PageRouteBuilder(
transitionDuration: Duration(milliseconds: 450),
reverseTransitionDuration: Duration(milliseconds: 450),
pageBuilder: (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return MonthPage(
month: month,
);
},
transitionsBuilder: (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return FadeTransition(opacity: animation, child: child);
},
);
}
}
In order to get that pushing behind animation, you need to use CupertinoScaffold alongside with CupertinoPageScaffold, e.g.
#override
Widget build(BuildContext context) {
return CupertinoScaffold(
transitionBackgroundColor: Colors.white,
body: Builder(
builder: (context) => CupertinoPageScaffold(
backgroundColor: Colors.white,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: ElevatedButton(
child: Text('show modal'),
onPressed: () =>
CupertinoScaffold.showCupertinoModalBottomSheet(
expand: true,
context: context,
backgroundColor: Colors.white,
builder: (context) => Container(
color: Colors.white,
child: Center(
child: ElevatedButton(
onPressed: () => Navigator.of(context)
.popUntil((route) =>
route.settings.name == '/'),
child: Text('return home'),
),
)),
)),
),
],
),
),
),
);
}

Flutter/Dart - Text value not showing correctly

I am trying to create a shopping cart using provider and display the number of items currently in the cart on my homepage. When I create my cart icon with a text widget overlaid, the value being shown does not reflect the number of items in the cart.
Here is my code:
class OurShoppingBasketIcon extends StatelessWidget {
const OurShoppingBasketIcon({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.center,
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => ShoppingBasketScreen()),
);
},
child: Stack(
children: <Widget>[
new Icon(
Icons.shopping_cart_outlined,
color: Colors.white,
),
new Positioned(
right: 0,
child: new Container(
padding: EdgeInsets.all(1),
decoration: new BoxDecoration(
color: Colors.red,
borderRadius: BorderRadius.circular(6),
),
constraints: BoxConstraints(
minWidth: 12,
minHeight: 12,
),
child: Text(
context.read<ShoppingBasket>().items.length.toString(),
style: new TextStyle(
color: Colors.white,
fontSize: 8,
),
textAlign: TextAlign.center,
),
),
)
],
),
),
);
}
}
This is where the icon is implemented:
class OurHomePage extends StatefulWidget {
#override
_OurHomePageState createState() => _OurHomePageState();
}
class _OurHomePageState extends State<OurHomePage> {
#override
Widget build(BuildContext context) {
return Consumer<OurUser>(
builder: (_, user, __) {
return ChangeNotifierProvider<SignInViewModel>(
create: (_) => SignInViewModel(context.read),
builder: (_, child) {
return Scaffold(
appBar: AppBar(
title: Text("My app"),
actions: [
OurShoppingBasketIcon(),
IconButton(
icon: Icon(
Icons.logout,
color: Colors.white,
),
onPressed: () {
context.read<FirebaseAuthService>().signOut();
},
),
],
),
);
},
);
},
);
}
}
There are 2 items in the cart as of writing this:
But the icon on the homepage does not change:
Here is my main.dart:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
MultiProvider(
providers: [
Provider(
create: (_) => FirebaseAuthService(),
),
StreamProvider<OurUser>(
create: (context) =>
context.read<FirebaseAuthService>().onAuthStateChanged),
ChangeNotifierProvider.value(
value: ShoppingBasket(),
),
],
child: MaterialApp(theme: OurTheme().buildTheme(), home: OurHomePage()),
),
);
}
perhaps if you watch for the value it will be updated dynamically:
context.watch<ShoppingBasket>().items.length.toString(), //<-- watch instead of read
The OurHomePage needs to be wrapped in the Provider<ShoppingBasket>.
return Provider<ShoppingBasket>(
create: (context) => ShoppingBasket(),
child: Consumer<OurUser>(
builder: (_, user, __) {
return ChangeNotifierProvider<SignInViewModel>(
create: (_) => SignInViewModel(context.read),
builder: (_, child) {
return Scaffold(
appBar: AppBar(
title: Text("My app"),
actions: [
OurShoppingBasketIcon(),
IconButton(
icon: Icon(
Icons.logout,
color: Colors.white,
),
onPressed: () {
context.read<FirebaseAuthService>().signOut();
},
),
],
),
),
);
},
);
I forgot to NotifyListeners() in my Change Notifier class:
class ShoppingBasket extends ChangeNotifier {
Map<String, SingleBasketItem> _items = {};
Map<String, SingleBasketItem> get items {
return {..._items};
}
void addItem(String id) {
_items.putIfAbsent(
id,
() => SingleBasketItem(id),
);
notifyListeners(); //HERE
}

Flutter, how to call Dialog function from another class

what is the proper way to call Dialog function from another class.
I have been searching this topic for a while but seems none of them are my answer.
my Dialog has a little complicated logic for server communicating and some paginations
so this code is going to be long for just one dart file. so I want to separate them.
and I need the some dialog animations so I picked the showGeneralDialog()
I also saw the example dialog implementaion using StatefulBuilder() which can use setState,
but this problem is it is not able to use initState()
for now, what I did is below
dart1 file
import 'package:aaa/bbb/some_dialog_file.dart'
as someDialog;
GestureDetector(
onTap: () async{
var result =
await someDialog.displayDialogOKCallBack(
context,
);
},
child: Container(
width: 60,
height: 60,
child: Icon(
Icons.comment,
size: 38,
),
),
)
dart2 file
Future<dynamic> displayDialogOKCallBack(BuildContext context) async {
return await showGeneralDialog(
barrierLabel: "Label",
barrierDismissible: true,
// barrierColor: ,
transitionDuration: Duration(milliseconds: 400),
context: context,
pageBuilder: (context, anim1, anim2) {
return StatefulBuilder(builder: (context, setState) {
return Scaffold(
body: SafeArea(
),
);
});
},
transitionBuilder: (context, anim1, anim2, child) {
return SlideTransition(
position:
Tween(begin: Offset(0, 1), end: Offset(0, -0.02)).animate(anim1),
child: child,
);
},
);
}
so my question is I want to build very clean animation dialog
which is logically separated from base class file and it has to have initState(), and setState()
how could I acheive this ? thanks
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Container(
child: RaisedButton(
onPressed: () {
someDialog(context);
},
child: Text("click"),
),
);
}
Future<dynamic> someDialog(BuildContext context) async {
return await showGeneralDialog(
barrierLabel: "Label",
barrierDismissible: true,
context: context,
pageBuilder: (context, anim1, anim2) {
return Scaffold(
backgroundColor: Colors.transparent,
body: SafeArea(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
// List
AnotherClassDialog(),
],
),
],
),
),
),
);
});
}
}
class AnotherClassDialog extends StatefulWidget {
#override
_AnotherClassDialogState createState() => _AnotherClassDialogState();
}
class _AnotherClassDialogState extends State<AnotherClassDialog> {
Color color;
#override
void initState() {
// TODO: implement initState
super.initState();
color = Colors.black;
}
#override
Widget build(BuildContext context) {
return Center(
child: Column(
children: [
RaisedButton(
onPressed: () {
setState(() {
color = Colors.red;
});
},
),
Container(
width: 100,
height: 100,
color: color,
),
RaisedButton(
onPressed: () {
setState(() {
color = Colors.green;
});
},
)
],
),
);
}
}
I use a custom dialog in my app in some classes and had the same problem.
You should define a dialog and pass context and other variables to it and call it everywhere you want.
You can define a dialog like this :
showCustomDialog(BuildContext context, String title, String description) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(
title,
textAlign: TextAlign.right,
),
content: SingleChildScrollView(
child: Text(
description,
style: Theme.of(context).textTheme.bodyText1,
textAlign: TextAlign.right,
),
),
actions: [
FlatButton(
child: Text(
'ok',
style: Theme.of(context).textTheme.bodyText2.copyWith(
color: Theme.of(context).accentColor,
),
),
onPressed: () => Navigator.of(context).pop(),
),
],
actionsPadding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 5,
),
);
});
}
and use it everywhere you want like this :
InkWell(
child: Icon(
Icons.error_outline,
size: 17,
),
onTap: () => showCustomDialog(context,"text1" , "text2") ,
),
I hope my answer will help you.

Why isn't the Provider Package changing the background image?

I've added the provider package to my application where I have two screen. When the user clicks on a small image on the app it changes the main background image on the other screen. I've called the Provider and classes on both screens but it just isn't returning the 'myValue' inside Positioned.fill.
Homepage screen with the background image that needs to change:
import 'package:flutter/material.dart';
import 'package:flutter_app_background/small_images.dart';
import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<MyModel>(
create: (context) => MyModel(),
child: MaterialApp(
title: 'Title',
home: HomePage(),
),
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
appBar: AppBar(
title: Text('Background Image', style: TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.bold),
),
iconTheme: IconThemeData(color: Colors.white),
actions: <Widget>[
IconButton(
icon: Icon(Icons.settings, color: Colors.black,),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SmallImages()),
);
},
),
],
backgroundColor: Colors.transparent,
elevation: 0.0,
),
body: Stack(
children: <Widget>
[
Positioned.fill(
child: GestureDetector(
child: Consumer<MyModel>(
builder: (context, myModel, child) {
return myModel.bgImage;
// return myValue;
},
),
),
),
],
),
);
}
}
class MyModel extends ChangeNotifier {
Image bgImage = Image.asset('images/background_image.jpeg', fit: BoxFit.fill);
}
Small Images screen where the user taps on small image to change the background in Homepage.
import 'package:flutter/material.dart';
import 'package:flutter_app_background/main.dart';
import 'package:provider/provider.dart';
class SmallImages extends StatefulWidget {
static int tappedGestureDetector = 1;
#override
_SmallImagesState createState() => _SmallImagesState();
}
class _SmallImagesState extends State<SmallImages> {
List<bool> isSelected;
void initState() {
isSelected = [true, false, false, false, false, false, false, false, false];
super.initState();
}
#override
Widget build(BuildContext context) {
final myModel = Provider.of<MyModel>(context,listen:true); //default for listen is `true`
return Scaffold(
appBar: AppBar(
title: Text('Small Image', style: TextStyle(
color: Colors.black, fontSize: 16, fontWeight: FontWeight.bold),
),
iconTheme: IconThemeData(color: Colors.white),
actions: <Widget>[
IconButton(
icon: Icon(Icons.arrow_left, color: Colors.black,),
onPressed: () {
Navigator.pop(
context,
MaterialPageRoute(builder: (context) => HomePage()),
);
},
),
],
backgroundColor: Colors.transparent,
elevation: 0.0,
),
body: Material(
child: GestureDetector(
child: MaterialApp(
builder: (context, snapshot) {
return GridView.count(
crossAxisCount: 1,
childAspectRatio: 1.0,
padding: const EdgeInsets.all(4.0),
mainAxisSpacing: 0.0,
crossAxisSpacing: 0.0,
children: [
GridView(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3,
childAspectRatio: MediaQuery
.of(context)
.size
.width /
(MediaQuery
.of(context)
.size
.height / 2),
),
children: [
GestureDetector(
onTap: () {
setState(() {
SmallImages.tappedGestureDetector = 1;
});
myModel.bgImage = Image.asset('images/iceland_background.jpg');
},
child: Container(
height: 100,
width: 107,
decoration: BoxDecoration(border: SmallImages
.tappedGestureDetector == 1
? Border.all(
color: Color(0xff2244C7), width: 1.0)
: Border
.all(color: Colors.transparent,),),
child: Image.asset(
'images/nightsky_image.png',
),
),
),
Consumer<MyModel>(
builder: (context, myModel, child) {
return GestureDetector(
onTap: () {
setState(() {
SmallImages.tappedGestureDetector = 2;
}); // <-- replaced 'tapped' and 'other'
},
child: Container(
height: 100,
width: 107,
decoration: BoxDecoration(border: SmallImages
.tappedGestureDetector == 2
? Border.all(
color: Color(0xff2244C7), width: 1.0)
: Border
.all(color: Colors.transparent,),),
child: Image.asset(
'images/own_image.png',
),
),
);
},
),
Consumer<MyModel>(
builder: (context, myModel, child) {
return GestureDetector(
onTap: () {
setState(() {
SmallImages.tappedGestureDetector = 3;
}); // <-- replaced 'tapped' and 'other'
},
child: Container(
height: 100,
width: 107,
decoration: BoxDecoration(border: SmallImages
.tappedGestureDetector == 3
? Border.all(
color: Color(0xff2244C7), width: 1.0)
: Border
.all(color: Colors.transparent,),),
child: Image.asset(
'images/iceland_image.png',
),
),
);
},
),
].toList(),
),
],
);
}),
),
),
);
}
}
You should wrap material app with provider:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Provider<MyModel>(
create: (context) => MyModel(),
child: MaterialApp(
title: 'Title',
home: HomePage(),
);
)
}
}
if you want it to rebuild some widget on change of the MyModel, you should extend MyModel with ChangeNotifer like this:
class MyModel extends ChangeNotifier{
final bgImage = //someimage
and instead of Provider around the MaterialApp you should use ChangeNotifierProvider like this:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<MyModel>(
create: (context) => MyModel(),
child: MaterialApp(
title: 'Title',
home: HomePage(),
);
)
}
}
after that you should include provider inside the widget like this:
#override
Widget build(BuildContext context) {
final mymodel = Provider.of<MyModel>(context); // injecting the provider
return Container ( .....
and you can use background image inside the container like this,
color:mymodel.bgImage
as soon as you will change the mymodel.bgImage widget will be rebuilt automatically.
if you are planning to rebuild specific widget inside the widget tree, you can remove injection such as this in this case final mymodel = Provider.of<MyModel>(context); and just wrap that specific widget with Consumer<MyModel> like this:
Container(
child:Consumer<MyModel>(
builder: (context, myModel, child) {
return Text("${myModel.text}"); // supposing that `text` is inside the `MyModel`
},
)
if you don't need child and context to use under the Container you can do it like this:
child:Consumer<MyModel>(
builder: (_, myModel, _) {
example of changing the myModel.bgImage will be something like this:
FlatButton(
onPressed: (val){
myModel.image = otherImage // changeing the value of my model while pressing on the button
}
)
changing the value of my model while pressing on the button will rebuild any widget which injects MyModel Provider and Provider listen property is set to true,(listen property is set to true by default)
example of listen property:
final mymodel = Provider.of<MyModel>(context,listen:false) //default for listen is `true`

Flutter Mobx multiple store not updating value in observer

I am using Mobx in my flutter application. I have multiple store. When I am trying to update some value in a store it is updating but not reflecting on UI.
main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider<LocationStore>(create: (_) => LocationStore()),
Provider<UserStore>(create: (_) => UserStore()),
Provider<RideStore>(create: (_) => RideStore()),
],
child: MaterialApp(
title: 'FLutter APp',
theme: myTheme(),
debugShowCheckedModeBanner: false,
// home: HomePage(
// title: 'Live Location'
// ),
initialRoute: WelcomeScreen.id,
routes: {
HomePage.id: (context) => HomePage(title: 'Home',),
ProfileScreen.id: (context) => ProfileScreen(),
WelcomeScreen.id: (context) => WelcomeScreen(),
RegistrationScreen.id: (context) => RegistrationScreen(),
LoginScreen.id: (context) => LoginScreen(),
FeedScreen.id: (context) => FeedScreen(),
RecordTrackScreen.id: (context) => RecordTrackScreen(),
SaveActivityScreen.id: (context) => SaveActivityScreen(),
UserProfileScreen.id: (context) => UserProfileScreen(),
RideDetailsScreen.id: (context) => RideDetailsScreen(),
StatsScreen.id: (context) => StatsScreen(),
FindInviteScreen.id: (context) => FindInviteScreen(),
CommentsScreen.id: (context) => CommentsScreen(),
},
),
);
}
}
screen
#override
Widget build(BuildContext context) {
final store = Provider.of<LocationStore>(context);
final rideStore = Provider.of<RideStore>(context);
final userStore = Provider.of<UserStore>(context);
return Scaffold(
appBar: AppBar(
title: Text('Record'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.info_outline),
onPressed: () {},
)
],
),
body: Observer(
builder: (_) => SingleChildScrollView(
child: Center(
child: Column(
children: <Widget>[
// Text('isPermitted - ${isPermitted}'),
Container(
child: isPermitted != null && isPermitted ? WeatherMainWidget() : Text(''),
height: (MediaQuery.of(context).size.height - 50.0) * 0.4,
),
Container(
height: (MediaQuery.of(context).size.height - 50.0) * 0.45,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
// Text('rideStore.getIsStarted - ${rideStore.getIsStarted} - $_isRunning'),
// if (rideStore.isStarted)
Divider(
indent: 10.0,
endIndent: 10.0,
thickness: 1.0,
color: color8,
),
Observer(
builder: (_) => Row(
children: [
Text('${rideStore.getSport.name}'),
Expanded(
flex: 1,
child: CustomIconButton(
iconData: rideStore.getSport.icon,
iconColor: Theme.of(context).primaryColor,
bgColor: Colors.transparent,
onPress: () {
SportBottomSheet(context, rideStore);
},
),
),
],
),
),
Container(
child: Visibility(
child: RecordTrackScreen(),
visible: rideStore.isStarted,
),
),
if (!rideStore.isStarted)
Container(
width: 70.0,
height: 70.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(50.0),),
color: Theme.of(context).primaryColor,
),
child: Visibility(
visible: !rideStore.isStarted,
child: CustomIconButton(
iconData: Icons.play_arrow,
iconColor: Colors.white,
iconSize: 35.0,
onPress: () {
print('dsdsadsadsad');
startTrip(store, rideStore, userStore);
},
),
),
),
],
),
),
// ListenLocationWidget()
],
),
),
),
),
);
}
Widget SportBottomSheet(BuildContext context, RideStore rideStore) {
showModalBottomSheet(context: context, builder: (BuildContext bc) {
return Container(
color: Colors.white,
child: ListView.builder(itemCount: 2, itemBuilder: (ctx, index) {
final sport = rideStore.sports[index];
return GestureDetector(
child: ListTile(
leading: Icon(sport.icon, size: 30.0, color: Colors.black,),
title: Text(sport.name, style: TextStyle(
fontSize: 20.0,
color: Colors.black
),),
onTap: () {
print('SportBottomSheet $sport');
rideStore.selectedSport = new Sport(
sport.icon,
sport.name
);
rideStore.recentSport = rideStore.recentSport;
Navigator.pop(context);
print('sport ${rideStore.getSport.name}');
},
),
);
}),
);
});
}
ride store
class Sport{
final IconData icon;
final String name;
Sport(this.icon, this.name);
}
List<Sport> sports = [
new Sport(Icons.directions_walk_outlined, 'Walk'),
new Sport(Icons.motorcycle, 'Ride'),
];
#observable
Sport recentSport;
#observable
Sport selectedSport;
#computed
Sport get getSport {
if (selectedSport != null)
return selectedSport;
if (recentSport != null)
return recentSport;
return sports[0];
}
When I am checking getSport value in onTap method it is showing selected value but it is not updating on UI.
Thanks
Try changing your #computed to #action
#action
Sport get getSport {
if (selectedSport != null)
return selectedSport;
if (recentSport != null)
return recentSport;
return sports[0];
}
or maybe use ObservableList or ObservableFuture
Use ObservableList like this
#observable
ObservableList<Sport> sports =[Sport(Icons.directions_walk_outlined, 'Walk'), new Sport(Icons.motorcycle, 'Ride'),];