Say I have this class:
class AppTheme {
final BuildContext context;
AppTheme(this.context);
TextStyle caption() {
return Theme.of(context).textTheme.caption.copyWith(
color: Colors.black
);
}
}
How can I modify it in a way that I can access caption using:
AppTheme.of(context).caption();
I'm not sure why would you need it that way, when you can already have it easily using
AppTheme(context).caption();
But if you really need it, you can try this:
class AppTheme {
final BuildContext context;
AppTheme._(this.context); // make this constructor private
static AppTheme of(BuildContext context) => AppTheme._(context); // pass context to above constructor
TextStyle caption() {
return Theme.of(context).textTheme.caption.copyWith(color: Colors.black);
}
}
And you can use it with
AppTheme.of(context).caption();
Related
How do you apply a method to a static variable from a class. There are certain built in classes in flutter that appear to do something like this.
class UITextStyle {
static const TextStyle body = TextStyle(fontSize: 17);
addColor(Color color) {
TextStyle style = this as TextStyle;
style.merge(TextStyle(color: color));
}
}
Which can then be called like this:
UITextStyle.body.addColor(Color.fromRGBA(0,0,0,1));
However I cannot call that method like that as firstly it is not static, and secondly if it were I would not be able to call it after declaring .body first and would only be able to call it at UITextStyle.addColor(...).
How is this achieved?
you can try this solution , the point is that addColor function is not defined to the TextStyle Type , so to achieve that you need to add this function to the TextStyle class by this extension :
extension TextStyleEx on TextStyle{
TextStyle addColor(Color color) {
return merge(TextStyle(color: color,fontWeight: FontWeight.w600));
}
}
and make this method return TextStyle so you can get instance from the merged ones , cause your static object is final so you can not receive new value on it.
and leave your class like this
class UITextStyle {
static const TextStyle body = TextStyle(fontSize: 17);
}
use this class and the saved static object to get new TextStyle with the old and the new TextStyles.
for test run this in main , will clear the previous example :
TextStyle mergedStyles = UITextStyl.body.addColor(Colors.black);
print(mergedStyles);
Thanks to the comments from #pskink I was eventually able to get this functioning.
class UITextStyle {
const UITextStyle(this.style);
final TextStyle style;
static const body = UITextStyle(TextStyle(fontSize: 17));
addColor(Color color) {
TextStyle textStyle = style;
return textStyle.merge(TextStyle(color: color));
}
}
In Dart extensions can have static members.
extension UITextStyle on TextStyle {
static const body = TextStyle(fontSize: 17);
TextStyle addColor(Color color) {
return this.merge(TextStyle(color: color));
}
}
UITextStyle.body.addColor(Color.fromRGBO(0, 0, 0, 1));
I'm trying to change the variable from another stateful class.
class first extends statefulwidget {
bool text = false;
Widget build(BuildContext context) {
setState((){});
return Container(
child: text ? Text('Hello') : Text('check')
);
}
}
class second extends statefulwidget {
Widget build(BuildContext context) {
return Container(
child: IconButton(
onPressed: () {
first fir = first();
setState((){
fir.test = true;
});
}
)
);
}
}
widget shows only check not showing Hello
This is my code...Ignore spelling mistakes and camelcase
Give me the solutions if you know..
If you are trying to access data on multiple screens, the Provider package could help you. It stores global data accessible from all classes, without the need of creating constructors. It's good for big apps.
Here are some steps to use it (there is also a lot of info online):
Import provider in pubspec.yaml
Create your provider.dart file. For example:
class HeroInfo with ChangeNotifier{
String _hero = 'Ironman'
get hero {
return _hero;
}
set hero (String heroName) {
_hero = heroName;
notifyListeners();
}
}
Wrap your MaterialApp (probably on main.dart) with ChangeNotifierProvider.
return ChangeNotifierProvider(
builder: (context) => HeroInfo(),
child: MaterialApp(...),
);
Use it on your application! Call the provider inside any build method and get data:
#override
Widget build(BuildContext context){
final heroProvider = Provider.of<HeroInfo>(context);
return Column {
children: [
Text(heroProvider.hero)
]
}
}
Or set data:
heroProvider.hero = 'Superman';
try to reference to this answer, create function to set boolean in class1 and pass as parameter to class 2 and execute it :
typedef void MyCallback(int foo);
class MyClass {
void doSomething(int i){
}
MyOtherClass myOtherClass = new MyOtherClass(doSomething);
}
class MyOtherClass {
final MyCallback callback;
MyOtherClass(this.callback);
}
I have a custom widget that uses a ListTile. I would like to set the Leading: property to a Checkbox if the Class A is building the widget, but set the Leading property to Null if Class B is building the widget.
Is it possible for the ListTile to know the name of the class that is building it?
Or is there a better way to approach this type of problem?
You can either use the is operator or use obj.runtimeType to check the type of object.
Refer to this link to understand the difference between them.
Here's an example snippet.
class CustomListTile{
var obj;
CustomListTile(this.obj);
void isSameClass(){
// if(obj.runtimeType == Truck)
if(obj is Truck){
print("Building checkbox");
}else{
print("returning Null");
}
}
}
class Chopper{
void test(){
CustomListTile obj = CustomListTile(this);
obj.isSameClass();
}
}
class Truck{
void test(){
CustomListTile obj = CustomListTile(this);
obj.isSameClass();
}
}
void main(){
Chopper objChop = Chopper();
objChop.test();
Truck objTruck = Truck();
objTruck.test();
}
Would passing a boolean like this do the job for you?
class CustomListTile extends StatelessWidget {
const CustomListTile({Key? key, this.hasLeading = false}) : super(key: key);
final bool hasLeading;
#override
Widget build(BuildContext context) {
return ListTile(
leading: hasLeading ? const Icon(Icons.person) : null,
);
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ListView(
children: [
CustomListTile(hasLeading: true), // This one has leading
CustomListTile(), // This one does not
],
);
}
}
So I have written this code below to make an Icon Stateful inside a Stateless widget.
class IconState extends StatefulWidget {
final bool isSelected;
IconState({
this.isSelected,
});
_IconState state; // this is not final because I need to assign it below
void toggle() {
state.change();
}
#override
_IconState createState() => state = new _IconState(
isSelected: this.isSelected,
);
}
class _IconState extends State<IconState> {
_IconState({
this.isSelected,
});
bool isSelected = false;
Widget _unSelected = Icon(
null,
);
Widget _selected = Icon(
Icons.check_outlined,
color: Colors.red,
);
void change() {
setState(() {
this.isSelected = this.isSelected == true ? false : true;
});
}
Icon evaluate() {
if (isSelected) {
return _selected;
}
return _unSelected;
}
#override
Widget build(BuildContext context) {
return evaluate();
}
}
To update the state of the Icon, I call the toggle() method from my Stateless widget.
Dart is giving me a non-final instance warning inside an #immutable class, but I am unable to find a workaround for this.
I have tried following:
final _IconState state = new _IconState(
isSelected: this.isSelected, // throws an error => Invalid reference to 'this' expression.
);
also this, but doesn't work either
final _IconState state;
IconState({this.isSelected}) {
this.state = new _IconState(
isSelected: this.isSelected,
);
};
Is there a workaround?
I would put the isSelected boolean inside an external state management class, then you can return 2 separate widgets in response to the change. Otherwise you would have to change the state inside of the widget where the icon will be displayed. Something like this:
class IconState extends ChangeNotifier{
bool _isSelected;
//any other needed state
bool get isSelected => _isSelected;
void changeIsSelected(bool selected) {
_isSelected = selected;
notifyListeners();
}
}
Then use ChangeNotifierProvider to to inject the state and call the change method.
final iconStateProvider = ChangeNotifierProvider((ref) => IconState());
Now, you can use iconStateProvider to access the state and methods. You will need a Builder or Consumer widget to listen for changes to the state.
Consumer( builder: (context, watch, child) {
final iconState = watch(iconStateProvider);
if (iconState.isSelected) {
return Icon();
} else {
return null;
}
This is using the Riverpod library, which is only 1 of many external state management libraries. I recommend watching tutorials on YouTube on different libraries and pick one that best suits you.
I have a provider that is listening for changes to a playerList class:
import 'package:flutter/material.dart';
import 'package:scam_artist/constants/PlayerColors.dart';
import 'package:scam_artist/models/Player.dart';
class PlayerList with ChangeNotifier {
List<Player> _players = [];
List<Player> get players {
return [..._players];
}
void addPlayer(Key key, String name, int index) {
_players.add(
new Player(key: key, color: PlayerColors.colors[index], name: name));
notifyListeners();
}
void editPlayerName(String newName, int index) {
_players[index].name = newName;
notifyListeners();
}
void editPlayerColor(Color newColor, int index) {
_players[index].color = newColor;
notifyListeners();
}
}
However, when I call a function to change a value to one of the Player objects (change name for example), the list doesn't update the object with new data.
Do I need another provider for the Player class? If so, how do I make the PlayerList provider listen for changes in the Player provider?
Doing a little research, I'm thinking ProxyProvider might be what I'm looking for, but I'm not sure how to implement it.
Here's my Player class if that is helpful:
import 'package:flutter/material.dart';
class Player {
// id will be for database if implemented
String uid;
Key key;
String name;
//position is the order where the player plays
int position;
Color color;
bool isTurn;
bool isFakeArtist;
bool isWinner;
Player({this.key, this.name, this.color});
}
And this is where I create the ChangeNotifierProvider:
import 'package:flutter/material.dart';
import 'package:scam_artist/UserListener.dart';
import 'package:scam_artist/models/user.dart';
import 'package:scam_artist/providers/PlayerList.dart';
import 'package:scam_artist/services/AuthService.dart';
import 'package:provider/provider.dart';
import 'package:scam_artist/views/Lobby.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<User>.value(value: AuthService().user),
ChangeNotifierProvider(create: (context) => PlayerList())
],
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: UserListener(),
routes: {Lobby.routeName: (ctx) => Lobby()},
),
);
}
}
There are multiple things wrong here.
1. Fix your PlayerList declaration line.
//CORRECT
class PlayerList extends ChangeNotifier {...}
//WRONG
class PlayerList with ChangeNotifier {...}
4. Fix your players getter line.
Use this:
List<Player> get players => _players;
3. You need more Conceptual Knowledge on Provider.
Basically, Provider makes your PlayerList accessible from anywhere within the Widget Tree below where you have provided it. For example, you are providing from the top of your MaterialApp. So you can access it in your HomePage or Lobby.
To access your PlayerList, you have to use a Consumer widget or Selector widget, but for your case, Consumer is enough. Selector is for advanced usage.
Here is the code for reading live values from your PlayerList.
class Lobby extends StatelessWidget {
#override
Widget build(BuildContext context) => Scaffold(
body: _body(),
);
_body() => Container(
child: Consumer<PlayerList>(
builder: (BuildContext context, PlayerList bloc) =>
ListView.builder(
itemCount: bloc.players.length,
itemBuilder:
(BuildContext context, int index) => Text(bloc.players[index].name),
),
),
);
}
I am having exactly the same problem.
One way I solved it (not the prettiest though) is to have a method in the ChangeNotifier to return a specific object and then watch for changes, e.g.
// in ChangeNotifier
Player getPlayer(int index) => _players[index];
and then in the widget have
context.watch<PlayerList>().getPlayer(index).updateName("newName")
If you are modifying from parent PlayerList then this is the way because parent is already notifying in your case
import 'package:flutter/material.dart';
class Player with ChangeNotifier {
Player({String name, Color color}) {
this.color = color;
this.name = name;
}
String uid;
Key key;
String _name;
String get name => _name;
set name(String value) {
_name = value;
notifyListeners();
}
int position;
Color _color;
bool isTurn;
bool isFakeArtist;
bool isWinner;
Color get color => _color;
set color(Color value) {
_color = value;
notifyListeners();
}
}
If you are modifying from within Player and want parent PlayerList to notify
import 'package:flutter/material.dart';
class Player with ChangeNotifier {
Player({String name, Color color, this.playerList}) {
this.color = color;
this.name = name;
}
final PlayerList playerList;
String uid;
Key key;
String _name;
String get name => _name;
set name(String value) {
_name = value;
notifyListeners();
playerList.notifyListeners();
}
int position;
Color _color;
bool isTurn;
bool isFakeArtist;
bool isWinner;
Color get color => _color;
set color(Color value) {
_color = value;
notifyListeners();
playerList.notifyListeners();
}
}
Just change your _players accessor and you should be good.
List<Player> get players {
return _players;
}