I am new to flutter and I am trying to use get_it package for DI. I want to understand how can I replace my dependance on the Inherited Widget with get_it.
My code looks like this:
I have a locator file which initialises the locator instance and its setup.
locator.dart file:
final locator = GetIt.instance;
void setupLocator() {
locator.registerLazySingleton<ExampleProvider>(() => ExampleProvider());
}
example_provider.dart:
class ExampleProvider with ChangeNotifier {
bool _value = false;
bool get value => _value;
set setValue(bool newValue) {
_value = newValue;
notifyListeners();
}
}
This is the HomePage where provide the ChangeNotifierProvider with ExampleProvider to the child widget.
home_page.dart file:
class HomePage extends StatefulWidget {
final String title;
const HomePage({Key? key, required this.title}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final _exampleProvider = locator.get<ExampleProvider>();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
key: Key('issue_list_screen_column'),
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
ChangeNotifierProvider(
create: (context) => _exampleProvider,
child: ExampleWidget(
key: Key('example_widget'),
),
),
],
),
appBar: AppBar(
title: Text(widget.title),
),
);
}
}
This is the ExampleWidget where I want to use the locator instead of Provider.of<ExampleProvider>(context)
example_widget.dart file:
class ExampleWidget extends StatefulWidget {
const ExampleWidget({
Key? key,
}) : super(key: key);
#override
_ExampleWidgetState createState() => _ExampleWidgetState();
}
class _ExampleWidgetState extends State<ExampleWidget> {
#override
Widget build(BuildContext context) {
// How do I use locator over here with context??
final _exampleProvider = Provider.of<ExampleProvider>(context);
return Switch(
value: _exampleProvider.value,
onChanged: (newValue) {
themeProvider.setValue = newValue;
},
);
}
}
So, I want to use locator for the line final _exampleProvider = Provider.of<ExampleProvider>(context); in ExampleWidget. How can I do that?
It does not work like this: final _exampleProvider = locator.get<ExampleProvider>();
try this
final _exampleProvider = locator.get<ExampleProvider>();
Related
I am trying to write a Typehead package. In case of that, I need to pass functions with generic parameters but I am getting a type error.
Here is my simplified code:
Models:
class BaseModel {
BaseModel({required this.title});
String title;
}
class SearchModel extends BaseModel {
SearchModel({required super.title, required this.myText});
String myText;
}
TypeHead Class:
class HorizontalTypeHead<T extends BaseModel> extends StatefulWidget {
const HorizontalTypeHead({
Key? key,
required this.onLookup,
required this.onSelected,
}) : super(key: key);
final Future<Iterable<T>> Function(String value) onLookup;
final Function(T model) onSelected;
#override
State<HorizontalTypeHead> createState() => _HorizontalTypeHeadState<T>();
}
class _HorizontalTypeHeadState<T extends BaseModel> extends State<HorizontalTypeHead> {
Iterable<T> _data = [];
List<Widget> renderColumn(BuildContext context) {
List<Widget> list = [
TextField(
onChanged: (String? val) async {
if (val != null) {
_data = await widget.onLookup(val) as Iterable<T>;
setState(() {});
}
},
),
];
if (_data.isNotEmpty) {
list.add(
SizedBox(
height: 100,
child: ListView(
scrollDirection: Axis.horizontal,
children: _data
.map((e) => ResultWidget<T>(
model: e, onSelected: widget.onSelected))
.toList(),
),
),
);
}
return list;
}
#override
Widget build(BuildContext context) {
return Column(children: renderColumn(context));
}
}
Result Widget:
class ResultWidget<T extends BaseModel> extends StatelessWidget {
const ResultWidget({required this.model, required this.onSelected, Key? key})
: super(key: key);
final T model;
final Function(T selected) onSelected;
#override
Widget build(BuildContext context) {
return InkWell(
onTap: () => onSelected(model),
child: Container(
color: Colors.red,
height: 100,
width: 100,
child: Text(model.title),
));
}
}
My main goal is to access data in SearchModel (myText field). But when I am assigning SearchModel as type, I get a type error.
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
Future<Iterable<SearchModel>> onLookup(String pattern) async {
return [SearchModel(title: "title", myText: "myText")];
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
body: HorizontalTypeHead<SearchModel>(
onSelected: ((model) => print(model.myText)), //I need to get myText
onLookup: onLookup,
),
));
}
}
_TypeError (type '(SearchModel) => void' is not a subtype of type '(BaseModel) => dynamic')
I would like to ask what I need to do for accessing SearchModel without any error from main class. Thanks in advance!
I just made generic my state class and solved it :)
class _HorizontalTypeHeadState<T extends BaseModel>
extends State<HorizontalTypeHead<T>>
instead of
class _HorizontalTypeHeadState<T extends BaseModel>
extends State<HorizontalTypeHead>
I have a problem in my Flutter app with implement my custom Widget LifeViewList(). When I try do it createState()_HomePageState need some arguments, when I do it then I get a worrnig “Don’t put any logic in createState” .
class HomePage extends StatefulWidget {
const HomePage(
{ Key? key,
required this.user,
}) : super(key: key);
final User user;
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late ScrollController _scrollController;
double _scrollControllerOffset = 0.0;
_HomePageState(this.lifeView);
void _scrollListener() {
setState(() {
_scrollControllerOffset = _scrollController.offset;
});
}
#override
void initState() {
_scrollController = ScrollController();
_scrollController.addListener(_scrollListener);
super.initState();
}
final LifeView lifeView;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
CustomScrollView(
controller: _scrollController,
slivers: [
const LogoBanner(),
const Topic('How do you feel today?'),
const EmotionGrid(),
const SliverToBoxAdapter(
child: SizedBox(height: 20),
),
const Topic('A place to think about yourself.'),
LifeViewList(
LifeView(
title: lifeView.title,
image: lifeView.image,
id:lifeView.id,
),
),
const SingOutButton(),
],
),
FadeAppBar(scrollOffset: _scrollControllerOffset)
],
),
);
}
}
What I have to change ?
To access your widget variables in your state you can do widget.variable within your state class.
For example using user:
class HomePage extends StatefulWidget {
const HomePage({ Key? key, required this.user,}) : super(key: key);
final User user;
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Text(widget.user.username);
}
}
it is not updating my widget. If I click on the switch to turn _darkMode on the switch is always moving back (doesn't change)...
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class SettingsScreen extends StatefulWidget {
const SettingsScreen({Key? key}) : super(key: key);
#override
_SettingsScreenState createState() => _SettingsScreenState();
}
class _SettingsScreenState extends State<SettingsScreen> {
#override
Widget build(BuildContext context) {
bool _darkMode = false;
return Container(
child: ListView(
children: [
ListTile(
title: const Text('Lights'),
trailing: CupertinoSwitch(
value: _darkMode,
onChanged: (bool value) {
setState(() {
_darkMode = value;
print('DarkMode: $_darkMode');
});
},
),
),
],
));
}
}
_darkMode must be a field in the widget state. Move it outside the build method:
class _SettingsScreenState extends State<SettingsScreen> {
bool _darkMode = false;
#override
Widget build(BuildContext context) {
I declare a class variable for a StatefulWidget - in the code below it's someString.
Is it possible to use this variable in the build(…)-method without declaring it as static?
class MyClass extends StatefulWidget {
String someString;
MyClass() {
this.someString = "foo";
}
#override
_MyClassState createState() => _MyClassState();
}
class _MyClassState extends State<MyClass> {
_MyClassState();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("someString - how to access it here?!"),
// title: Text(someString), is not possible, obviously
),
);
}
}
Thanks in advance for help!
Attention: MyClass should be immutable.
1. If someString will never change
Keep it inside MyClass but define it as final.
class MyClass extends StatefulWidget {
final String someString;
const MyClass({Key key, this.someString = 'foo'}) : super(key: key);
#override
_MyClassState createState() => _MyClassState();
}
Then, inside the State, you can use it as widget.someString:
class _MyClassState extends State<MyClass> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('${widget.someString} is accessed here?!')),
);
}
}
2. If someString will change
It should be defined in the state.
class MyClass extends StatefulWidget {
final String initialValue;
const MyClass({Key key, this.initialValue = 'foo'}) : super(key: key);
#override
_MyClassState createState() => _MyClassState();
}
class _MyClassState extends State<MyClass> {
String someString;
#override
void initState() {
someString = widget.initialValue;
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('$someString is accessed here?!')),
body: Center(
child: OutlinedButton(
onPressed: () => setState(() => someString = 'NEW Value'),
child: Text('Update value'),
),
),
);
}
}
Reproducible Code:
void main() => runApp(MaterialApp(home: CountInheritedWidget(child: HomePage())));
class CountInheritedWidget extends InheritedWidget {
CountInheritedWidget({Widget child}) : super(child: child);
final Map<String, int> _map = {"count": 0};
// getter
int get value => _map["count"];
// setter
set value(int x) => _map["count"] = x; // is there anything like setState here?
#override
bool updateShouldNotify(CountInheritedWidget oldCounter) => true;
static CountInheritedWidget of(BuildContext context) => context.dependOnInheritedWidgetOfExactType<CountInheritedWidget>();
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextWidget(),
ButtonWidget(),
],
),
),
);
}
}
class TextWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
int count = CountInheritedWidget.of(context)?.value ?? -1;
return Text("Count = $count");
}
}
class ButtonWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
return RaisedButton(
child: Text("Increment"),
onPressed: () {
CountInheritedWidget counter = CountInheritedWidget.of(context);
int count = counter?.value ?? -1;
counter.value = ++count;
},
);
}
}
I'd like to update the value of count from ButtonWidget, I am sure it is getting updated in the CounterInheritedWidget class but it isn't reflecting on the screen. How can I call setState or something like that from InheritedWidget?
Any help will be appreciated, I am newbie to Flutter and Dart so having tough time in solving this kind of issue. Thank you and have a great day.
Note: I am not looking for some plugins like Provider, ScopedModel, Redux for this kinda work.
InheritedWidgets cannot do that. They are completely immutable with no mechanism for triggering updates.
If you want to emit updates, you will have to combine your InheritedWidget with a StatefulWidget, typically done in such way:
class MyWidget extends StatefulWidget {
const MyWidget({Key key, this.child}) : super(key: key);
final Widget child;
#override
MyState createState() => MyState();
}
class MyState extends State<MyWidget> {
String name;
int age;
#override
Widget build(BuildContext context) {
return MyInherited(
name: name,
age: age,
child: widget.child,
);
}
}
Where MyInheritedWidget is:
class MyInherited extends InheritedWidget {
MyInherited({
Key key,
this.name,
this.age,
Widget child,
}) : super(key: key, child: child);
final String name;
final int age;
#override
bool updateShouldNotify(MyInherited oldWidget) {
return name != oldWidget.name && age != oldWidget.age;
}
#override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(IntProperty('age', age));
properties.add(StringProperty('name', name));
}
}
Yup. That's verbose. Which is why provider exists.
Here is a complete example:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: MyStateful(
child: Column(
children: <Widget>[
MyCounter(),
MyButton(),
],
),
),
),
);
}
}
// MyStateful and MyInherited together act like a Provider
class MyStateful extends StatefulWidget {
const MyStateful({Key? key, required this.child}) : super(key: key);
final Widget child;
#override
MyState createState() => MyState();
}
class MyState extends State<MyStateful> {
int _count = 0;
void increment() {
setState(() {
_count += 1;
});
}
#override
Widget build(BuildContext context) {
return MyInherited(
count: _count,
increment: this.increment,
child: widget.child,
);
}
}
// Whenever state values are changes a new MyInherited is created
// with new parameters.
class MyInherited extends InheritedWidget {
MyInherited({
Key? key,
required this.count,
required this.increment,
required Widget child,
}) : super(key: key, child: child);
final int count;
final void Function() increment;
#override
bool updateShouldNotify(MyInherited oldWidget) {
return count != oldWidget.count;
}
static MyInherited of(BuildContext context) {
final MyInherited? result =
context.dependOnInheritedWidgetOfExactType<MyInherited>();
assert(result != null, 'No MyInherited found in context');
return result!;
}
}
class MyCounter extends StatelessWidget {
const MyCounter({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Text('Count: ${MyInherited.of(context).count}');
}
}
class MyButton extends StatelessWidget {
const MyButton({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return TextButton(
onPressed: () {
MyInherited.of(context).increment();
},
child: const Text('Increment'),
);
}
}