Hi im new to flutter and making a drawing app. I making a Icon button that show overlay implemented Slider widget.
this is my code and Im using a Provider package.
main.dart
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home:
ChangeNotifierProvider(
create: (context) => DrawingProvider(),
child: BlankPage()),
),
);
}
// Main Page
class BlankPage extends StatefulWidget {
const BlankPage({Key? key}) : super(key: key);
#override
State<BlankPage> createState() => _BlankPageState();
}
class _BlankPageState extends State<BlankPage> {
#override
Widget build(BuildContext context) {
var p = Provider.of<DrawingProvider>(context);
return GestureDetector(
child: Scaffold(
appBar: AppBar(title: Text('Workbook Test')),
body: Column(
children: [
Container(
width: double.infinity,
height: 50,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
penWidget(p: p),
highlighterWidget(p: p),
erasePenWidget(p: p),
SliderOverlayWidget(),
.
.
.
// ... Slider Overlay Widget I tried to make
class SliderOverlayWidget extends StatefulWidget {
const SliderOverlayWidget({
Key? key,
}) : super(key: key);
#override
State<SliderOverlayWidget> createState() => _SliderOverlayWidgetState();
}
class _SliderOverlayWidgetState extends State<SliderOverlayWidget> {
OverlayEntry? entry;
#override
void initState() {
super.initState();
WidgetsBinding.instance!.addPostFrameCallback((_) => showOverlay() );
}
#override
Widget build(BuildContext context) {
return Container(
width: 60,
child: FittedBox(
fit: BoxFit.fitWidth,
child: GestureDetector(
onTap: () {
showOverlay();
},
child: Icon(
Icons.horizontal_rule_rounded,
color: Colors.black54,
),
),
),
);
}
void showOverlay() {
final overlay = Overlay.of(context)!;
entry = OverlayEntry(builder: (context) => buildSliderOverlay(),);
overlay.insert(entry!);
}
}
StatefulWidget buildSliderOverlay() {
return StatefulBuilder(
builder: (context, setState) {
var p = Provider.of<DrawingProvider>(context);
return Container(
width: 100,
child: Row(
children: [
Slider(value: p.penSize, onChanged: (size) {
p.changePenSize = size;
},
min: 3,
max: 15,)
],
),
);
},
);
}
DrawingProvider.dart
class DrawingProvider extends ChangeNotifier {
// pen size
double _penSize = 3;
double get penSize => _penSize;
set changePenSize(double size) {
_penSize = penSize;
notifyListeners();
}
when I run the App, errors are like
Erros
Error: Could not find the correct Provider<DrawingProvider> above this StatefulBuilder Widget
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 StatefulBuilder is under your MultiProvider/Provider<DrawingProvider>.
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>().toString()),
);
}
```
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, child) {
// No longer throws
return Text(context.watch<Example>().toString());
}
);
}
```
If none of these solutions work, consider asking for help on StackOverflow:
Thank you
I referenced this
https://www.youtube.com/watch?v=OOEyJ0ct0Sg
How to show Slider dialog widget in flutter
Related
I'm trying to get full control on routes using Navigator 2.0 api. I want some pages to have transparent background (e.g. dialogs and bottom sheets) but still be represented as pages in Navigator. Naive way - just add a MaterialPage with partially transparent widget - doesn't work, lower page becomes black after transition animation.
Minimal code for reproduction is below. I expect to see red square (UpperPage) on green background (RootPage) but background becomes black. Navigator 1.0 api, like showGeneralDialog, works fine with this case, but I don't want to mix declarative and imperative way since it's hard to control from single source of truth like bloc or Provider. Is there any way to achieve this behaviour with pages api only?
class RootPage extends StatelessWidget {
const RootPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return SizedBox.expand(
child: Container(color: Colors.green),
);
}
}
class UpperPage extends StatelessWidget {
const UpperPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: Container(
width: 100,
height: 100,
color: Colors.red,
),
);
}
}
class AppRouterDelegate extends RouterDelegate<String> with ChangeNotifier {
#override
Widget build(BuildContext context) {
return Navigator(
pages: const [
MaterialPage(child: RootPage()),
MaterialPage(child: UpperPage()),
],
onPopPage: (route, result) {
return route.didPop(result);
},
);
}
...
}
import 'package:flutter/material.dart';
class TutorialOverlay extends ModalRoute<void> {
#override
Duration get transitionDuration => Duration(milliseconds: 500);
#override
bool get opaque => false;
#override
bool get barrierDismissible => false;
#override
Color get barrierColor => Colors.black.withOpacity(0.5);
#override
String get barrierLabel => null;
#override
bool get maintainState => true;
#override
Widget buildPage(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
// This makes sure that text and other content follows the material style
return Material(
type: MaterialType.transparency,
// make sure that the overlay content is not cut off
child: SafeArea(
child: _buildOverlayContent(context),
),
);
}
Widget _buildOverlayContent(BuildContext context) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(
'This is a nice overlay',
style: TextStyle(color: Colors.white, fontSize: 30.0),
),
RaisedButton(
onPressed: () => Navigator.pop(context),
child: Text('Dismiss'),
)
],
),
);
}
#override
Widget buildTransitions(
BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
// You can add your own animations for the overlay content
return FadeTransition(
opacity: animation,
child: ScaleTransition(
scale: animation,
child: child,
),
);
}
}
// Example application:
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Playground',
home: TestPage(),
);
}
}
class TestPage extends StatelessWidget {
void _showOverlay(BuildContext context) {
Navigator.of(context).push(TutorialOverlay());
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Test')),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Center(
child: RaisedButton(
onPressed: () => _showOverlay(context),
child: Text('Show Overlay'),
),
),
),
);
}
}
I think it's very normal to use StatefulWidget to implement the following in an app that controls ListView scrolling.
//sample(a)
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
MaterialApp(
home: HomePage(),
),
);
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
ElevatedButton(
child: Text('Go to ListViewPage'),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
ChangeNotifierProvider<ListViewPageController>(
create: (_) => ListViewPageController(),
child: ListViewPage(),
),
),
);
},
),
],
),
),
);
}
}
class ListViewPage extends StatefulWidget {
const ListViewPage({Key? key}) : super(key: key);
#override
State<ListViewPage> createState() => _ListViewPageState();
}
class _ListViewPageState extends State<ListViewPage> {
final ScrollController sc = ScrollController();
#override
void dispose() {
sc.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: ListView.builder(
controller: sc,
itemCount: 100,
itemBuilder: (context, index) {
return ListTile(title: Text('$index'));
}),
);
}
}
On the other hand, I think that the following implementation that manages ListViewPage scrolling using ChangeNotifier/Provider (Not Riverpod) without using StatefulWidget can also be considered. The actual execution itself is done without problems.
////sample(b)
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ListViewPageController extends ChangeNotifier {
ScrollController sc = ScrollController();
}
void main() {
runApp(
MaterialApp(
home: HomePage(),
),
);
}
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
ElevatedButton(
child: Text('Go to ListViewPage'),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) =>
ChangeNotifierProvider<ListViewPageController>(
create: (_) => ListViewPageController(),
child: ListViewPage(),
),
),
);
},
),
],
),
),
);
}
}
class ListViewPage extends StatelessWidget {
const ListViewPage({Key? key}) : super(key: key);
//↓Is this part mandatory?
/*
late final ScrollController sc = context.read<ListViewPageController>().sc;
#override
void dispose() {
sc.dispose();
super.dispose();
}
*/
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: ListView.builder(
controller: context.read<ListViewPageController>().sc,
itemCount: 100,
itemBuilder: (context, index) {
return ListTile(title: Text('$index'));
}),
);
}
}
Question1
Sample(b) has the same behavior as sample(a), but since ListViewPage is StatelessWidget, sc.dispose is not called.
My current understanding is that the ListViewPageController will be destroyed when we back from ListViewPage to HomePage, but will the ScrollController be also automatically destroyed at that point?
Shouldn't I write the code about sc.dispose myself?
Or do I have to make the ListViewPage a StatefulWidget and write the code myself to call sc.dispose in state.dispose?
Question2
Recently, I heard the following claim (explanation).
"Subclasses of ChangeNotifier (or StateNotfier) should not depend on any UI code.
Flutter's ScrollController/PageController/TextEditingController/Forms, etc. are classes in the Flutter SDK (that is, UI code).
So in this example the ListViewPageController should not have(depend on) a ScrollController. ”
Is this correct?
I also saw another sample code that uses Scrollable.ensureVisible to control scrolling, but since this Scrollable class is also UI code, is it a bad practice to use it in a ChangeNotifier subclass as shown below?
(In this case each ListTile should have a GlobalKey.)
Personally, I think it would be easier to understand if the ChangeNotifier subclass held the ScrollController,PageController, etc.
How important is this theory when developing in Flutter?
class ListViewPageController extends ChangeNotifier {
ScrollController sc = ScrollController();
scrollRequest(int scrollIndex) {
Scrollable.ensureVisible(keyList[scrollIndex - 100].currentContext!,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
alignment: 0.5,
alignmentPolicy: ScrollPositionAlignmentPolicy.explicit);
}
}
Q1. In sample b, sc is an obj< ScrollController > in another obj< ListViewPageController >, let's say it's lc.
When lc is (disposed)->deleted, surely will sc be (disposed)->deleted.
Q2. Holding ScrollController in ListViewPageController may cause error when you accidentally dispose ListViewPageController while ListViewPage was still alive.
If you want to access ScrollController, try inject it into ListViewPageController would be a better practice.
I am using MVVM architecture and used stacked dependency for this. I want to call a method exist in ViewModel class from View class.
In this view class trigger method is Widget build(BuildContext context) So I am unable to get reference of ViewModel class.
Is there any way to achieve this.
For more details I have added my code for Stateless Widget:
class ECRView extends StatelessWidget {
#override
Widget build(BuildContext context) {
// TODO: implement build
SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);
return ViewModelBuilder<ECRViewModel>.reactive(
onModelReady: (model) {
model.init(context);
},
builder: (context, model, child) => Container(
padding: EdgeInsets.all(AppSize.extraSmall),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: screenSize.width,
height: 1.5,
color: Colors.black12,
),
SizedBox(
height: screenSize.height * .02,
),
],
),
),
viewModelBuilder: () => ECRViewModel(),
);
}
//Trigger ECR Model Method
getTriggered(){
//From here I want to call
}
}
This should work on your case, if you want to use the model on widgets
class _MyWidget extends ViewModelWidget<NameViewModel> {
#override
Widget build(BuildContext context, NameViewModelmodel model) {
//can call model
return //some code
}
}
This is not the best option for stacked architecture
class _MyWidget extends StatefulWidget {
final NameViewModelmodel model;
const _MyWidget({Key key, this.model}) : super(key: key);
#override
__MyWidgetState createState() => __MyWidgetState();
}
class __MyWidgetState extends State<_MyWidget> {
#override
Widget build(BuildContext context) {
return Container();
}
}
So I'm trying to make a list that contains some widgets and then add a new widget to it when I press a button, but it doesn't seem to be working
This is the code:
class MessagesProvider extends ChangeNotifier{
List<dynamic> mesgs = [
new chatBubbleSend(),
new chatBubbleReceiver(),
new chatBubbleReceiver()
];
bool loading = true;
addMesg(){
mesgs.add(chatBubbleSend());
print(mesgs.length);
print(mesgs);
notifyListeners();
}
printMesg(){
print(mesgs.length);
print(mesgs);
}
removeMesg(){
mesgs.removeLast();
print(mesgs.length);
print(mesgs);
notifyListeners();
}
}
and this is what i get when i press the add, remove or print buttons
add,remove,print
and this is the list builder code
ChangeNotifierProvider<MessagesProvider>(
create: (context) => MessagesProvider(),
child: ChatMessages()
),
class ChatMessages extends StatelessWidget {
#override
Widget build(BuildContext context) {
final mesgs = Provider.of<MessagesProvider>(context, listen: false).mesgs;
return ListView.builder(
shrinkWrap: true,
itemCount: mesgs.length,
itemBuilder: (context,index)=> mesgs[index],
);
}
}
I have looking for a solution for over 8 hours now, and still, I couldn't fix it.
I jumped the gun with my first answer sorry.
When trying to recreate I ran into the same frustrating issue - focusing on the the provider being the problem until I realised it's actually the rendering of the updated list that's the issue.
You need to use a list builder to render the updating list in a change notifier consumer in a stateful widget
Full working example below:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class WidgetListProvider with ChangeNotifier {
List<Widget> widgets = [];
int listLength = 0;
void addWidget(){
Widget _widget = Text('Hello');
widgets.add(_widget);
listLength = widgets.length;
print('Added a widget');
notifyListeners();
}
void removeWidget(){
if (widgets.length > 0) {
widgets.removeLast();
listLength = widgets.length;
print('Removed a widget');
notifyListeners();
}
}
}
class HomePage extends StatefulWidget {
HomePage({Key key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
Widget _appBar (BuildContext context) {
return AppBar(
title: Text('My App'),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: _appBar(context),
// You need to define widgets that update when a provider changes
// as children of a consumer of that provider
body: Consumer<WidgetListProvider>(builder: (context, widgetProvider, child){
return Container(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
RaisedButton(
child: Text('Add widget'),
onPressed: () {
widgetProvider.addWidget();
},
),
RaisedButton(
child: Text('Remove Widget'),
onPressed: () {
widgetProvider.removeWidget();
},
),
Row(
children: [
Text('Number of Widgets: '),
Text(widgetProvider.listLength.toString()),
],
),
Container(
height: MediaQuery.of(context).size.height*0.6,
child: ListView.builder(itemCount: widgetProvider.widgets.length, itemBuilder: (BuildContext context, int index){
return widgetProvider.widgets[index];
})
)
],
),
);
}
),
);
}
}
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => WidgetListProvider(),
child: MyApp(),
)
);
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
home: HomePage(),
);
}
}
I have a method in state class, but I need to access that method in outside using its widget class reference,
class TestFormState extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _testState();
}
}
class _testFormState extends State<TestFormState> {
int count = 1;
#override
Widget build(BuildContext context) {
return Center(
child: Container(
color: Colors.green,
child: Text("Count : $count"),
),
);
}
clickIncrease(){
setState(() { count += 1; });
}
}
and I need to access the above widget`s clickIncrease in another widget, like below code,
class TutorialHome extends StatelessWidget {
TestFormState test;
#override
Widget build(BuildContext context) {
// Scaffold is a layout for the major Material Components.
return Scaffold(
body: Column(
children: <Widget>[
test = TestFormState(),
FlatButton(
child: Text("Increase"),
onPressed: (){
test.state.clickIncrease(); // This kind of thing I need to do
},
),
]
),
);
}
I wrote above code just for demostrate the issue.
I have a trick, but I don't know if it is a bad practice or not.
class TestFormState extends StatefulWidget {
_TestFormState _testFormState;
#override
State<StatefulWidget> createState() {
_testFormState = _TestFormState();
return _testFormState;
}
}
class _TestFormState extends State<TestFormState> {
int count = 1;
#override
Widget build(BuildContext context) {
return Center(
child: Container(
color: Colors.green,
child: Text("Count : $count"),
),
);
}
clickIncrease(){
setState(() { count += 1; });
}
}
Now, you can access it here :
class TutorialHome extends StatelessWidget {
TestFormState test;
#override
Widget build(BuildContext context) {
// Scaffold is a layout for the major Material Components.
return Scaffold(
body: Column(
children: <Widget>[
TextButton(
child: Text("Increase"),
onPressed: () {
test._testFormState
.clickIncrease(); // This is accessable
},
),
]
),
);
}
}
I suggest taking a look at ValueNotifier
I think there is a better way to manage your app state in an easy way and I agree that using provider could be effective.
Provide the model to all widgets within the app. We're using
ChangeNotifierProvider because that's a simple way to rebuild
widgets when a model changes. We could also just use Provider, but
then we would have to listen to Counter ourselves.
Read Provider's docs to learn about all the available providers.
Initialize the model in the builder. That way, Provider can own
Counter's lifecycle, making sure to call dispose when not needed
anymore.
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => Counter(),
child: MyApp(),
),
);
}
Simplest possible model, with just one field. ChangeNotifier is a
class in flutter:foundation. Counter does not depend on Provider.
class Counter with ChangeNotifier {
int count = 1;
void clickIncrease() {
count += 1;
notifyListeners();
}
}
Consumer looks for an ancestor Provider widget and retrieves its
model (Counter, in this case). Then it uses that model to build
widgets, and will trigger rebuilds if the model is updated.
You can access your providers anywhere you have access to the context.
One way is to use Provider<Counter>.of(context).
The provider package also defines extension methods on context itself.
You can call context.watch<Counter>() in a build method of any
widget to access the current state of Counter, and to ask Flutter to
rebuild your widget anytime Counter changes.
You can't use context.watch() outside build methods, because that
often leads to subtle bugs. Instead, you should use
context.read<Counter>(), which gets the current state but doesn't
ask Flutter for future rebuilds.
Since we're in a callback that will be called whenever the user taps
the FloatingActionButton, we are not in the build method here. We
should use context.read().
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
// Scaffold is a layout for the major Material Components.
return Scaffold(
appBar: AppBar(
title: Text('Flutter Demo Home Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Count:'),
Consumer<Counter>(
builder: (context, counter, child) => Text(
'${counter.value}',
style: Theme.of(context).textTheme.headline4,
),
),
],
),
),
// I've change the button to `FloatingActionButton` for better ui experience.
floatingActionButton: FloatingActionButton(
// Here is the implementation that you are looking for.
onPressed: () {
var counter = context.read<Counter>();
counter.increment();
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Complete code:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => Counter(),
child: MyApp(),
),
);
}
class Counter with ChangeNotifier {
int count = 1;
void clickIncrease() {
count += 1;
notifyListeners();
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter Demo Home Page'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Count:'),
Consumer<Counter>(
builder: (context, counter, child) => Text(
'${counter.count}',
style: Theme.of(context).textTheme.headline4,
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
var counter = context.read<Counter>();
counter.clickIncrease();
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Actual app:
For more information on the provider package (where Provider comes from), please see the package documentation.
For more information on state management in Flutter, and a list of other approaches, head over to the State management page at flutter.dev.
There is a built in method findAncestorStateOfType to find Ancestor _MyAppState class of the Parent MyApp class.
Here is the Code
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
static void setLocale(BuildContext context, Locale locale) {
_MyAppState? state = context.findAncestorStateOfType<_MyAppState>();
state!.setLocale(locale);
}
#override
_MyAppState createState() => _MyAppState();
}
// ignore: use_key_in_widget_constructors
class _MyAppState extends State<MyApp> {
// const MyApp({Key? key}) : super(key: key)
late Locale _locale;
void setLocale(Locale value) {
setState(() {
_locale = value;
});
}
}
class TestForm extends StatelessWidget {
final int _count;
TestForm(int count) : _count = count;
#override
Widget build(BuildContext context) {
return Center(
child: Container(
color: Colors.green,
child: Text('Count : $_count'),
),
);
}
}
class TutorialHome extends StatefulWidget {
#override
State<TutorialHome> createState() => _TutorialHomeState();
}
class _TutorialHomeState extends State<TutorialHome> {
int _count = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
TestForm(_count), // <---
TextButton(
child: Text("Increase"),
onPressed: () => setState(() => _count++),
),
],
),
);
}
}