Force Flutter PopupMenuButton to repaint when locale (language is changed) - android

Does anybody know how do I get the list of CheckedPopupMenuItems to repaint (they obviously represent the list with options PopupMenuButton shows when pressed) when the list opened and visible at the very moment when I choose to change the locale/language of the android device?
For now when I do this everything on the screen gets repainted to reflect the change of the language, except the opened list. It gets repainted once I close it and open again.
Thanks for your suggestions!

You'll likely end up needing to use Keys. Basically your list (Even though information is being changed) is still technically the same widget as it was originally, and so your program doesn't rebuild the list when the information changes. If the rest of your app's widgets are being updated properly, that means you are setting the state properly, so adding the keys should be sufficient.
It'd be something like,
MapSample({Key key, this.title}) : super(key: key);
for more information on keys take a look at:
https://api.flutter.dev/flutter/widgets/Widget/key.html
Hope this helps!

Here's the code:
Widget _buildAndroid(BuildContext context) {
final width = MediaQuery.of(context).size.width;
return PopupMenuButton<ScreenOrientation>(
child: SizedBox(
width: width,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Icon(ScreenOrientation._getAndroidIconData(currentSelection), size: 30, color: iconColor),
SizedBox(width: 20),
Icon(Icons.expand_more, color: fontColor)
])),
padding: EdgeInsets.zero,
onSelected: (ScreenOrientation orientation) {
onScreenOrientationChanged(orientation);
},
itemBuilder: (BuildContext context) =>
ScreenOrientation.orientations
.map((o) => CheckedPopupMenuItem<ScreenOrientation>(
key: UniqueKey(),
selectedColor: selectedColor,
child: Row(
children: <Widget>[
Icon(ScreenOrientation._getAndroidIconData(o), size: 30, color: iconColor),
SizedBox(width: 10),
Text(L10n.of(context).tr((m) =>
m['settings']['orientations'][o._name]),
style: TextStyle(color: fontColor),
)
],
),
value: o,
checked: o == currentSelection,
))
.toList());
}
So the only interesting bit here is the L10n.of(context).tr((m) => m['settings']['orientations'][o._name] which essentially retrieves a localized string from the right json file whose name is determined based on the current locale.

Related

Correct way to manage state on dynamic widgets in Flutter

I have a list of categories in sqlite, I got those rows across streambuilder, then, I create a list of switch widgets. In each switch widget ontoggle event I change the source value and I call setState method, but, this causes the execution of the build event and reset all switch widgets values. After that I changed the code to store all widgets in a variable and in the streambuild if the widgets exists, return the widget list, this works almost well, this update the widgets values in sources, but, the widgets looks like if they were false.
Anyone has a clue?
Regards
StreamBuilder<List<Category>>( <--- this code is in build event
stream: _catBloc.categoriesStream,
builder: (BuildContext context, AsyncSnapshot<List<Category>> snapshot) {
if(!snapshot.hasData) {
return Container(
height: _size.height,
width: _size.width,
child: Center(
child: CircularProgressIndicator(),
),
);
} else if(_categoriesLinked.isEmpty){ <-- this is a map with the list of id and name
column = _createCategories(snapshot.data, widthSwitch);
return column;
} else {
return column;
}
},
),
Container( <-- this is inside _createCategories method
width: width,
child: Row(
children: [
FlutterSwitch(
width: _size.width * 0.06,
height: _size.height * 0.02,
toggleSize: 20.0,
value: _categoriesLinked[e.id],
showOnOff: false,
padding: 2.0,
activeColor: Color.fromRGBO(88, 203, 143, 0.25),
inactiveColor: Color.fromRGBO(224, 233, 240, 0.50),
activeToggleColor: Color.fromRGBO(88, 203, 143, 1.0),
inactiveToggleColor: Color.fromRGBO(78, 88, 96, 0.50),
onToggle: (val) => setState(() {
_categoriesLinked[e.id] = val;
}),
),
SizedBox(width: _size.width * 0.01,),
Flexible(child: Text("${ e.id}. ${ e.name}"))
]
),
)
In this case, you should refactor your code in order to create the switch as another widget, making them rebuilding only themselves.
For the state of the switch, you can simply get it from the parent using GlobalKey<MySwitchState> to access the children state during an onPressed method for example with: mySwitchKey.currentState
You should definitely avoid building stateful widget inside methods. If you feel the need of a method, create a new widget.

Flutter StreamProvider fails to update Widget state. How do I go around this?

I'm using a StreamProvider to capture changes that occur in Firestore and display them on the user homepage. When the user initially signs in or registers, they're directed to this homepage and expect to see their details, for which I use the StreamProvider.
Keeping in mind that the first value fetched will be null, I've added a Loading Text Widget. However, the widget does not seem to change unless I hot-refresh the page and the Stateful Widget gets rebuilt again.
What seems to be happening is that the Stateful Widget I'm using as my HomePage gets "built" only once due to which even when the stream gets populated with non-null values, the loading widget does not disappear because its parent widget doesn't get rebuilt.
This is the basic HomeScreen widget around which I'm wrapping my StreamProvider.
#Override
Widget build(BuildContext context) {
return StreamProvider<UserDataModel>.value(
initialData: null,
value: DatabaseServices(userDetails: _userDetails).userDataDocument,
updateShouldNotify: (previous, current) => true,
child: Scaffold(
backgroundColor: Colors.white,
body: HomeBody(),
)
);
}
This is the relevant snippet from the HomeBody widget
class HomeBody extends StatefulWidget {
#override
_HomeBodyState createState() => _HomeBodyState();
}
class _HomeBodyState extends State<HomeBody> {
#override
Widget build(BuildContext context) {
final AuthenticationServices _authServices = AuthenticationServices();
final UserDataModel _data = Provider.of<UserDataModel>(context);
print(_data);
print((_data != null) ? _data.name : 'NULL');
/* Some code */
Row(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(25, 0, 0, 5),
child: Text(_data != null ? _data.name : 'LOADING...', style: dashboardName),
),
],
),
Row(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(25, 2, 0, 5),
child: Text(_data != null ? _data.scholarId : 'LOADING...', style: dashboardName),
),
],
),
I also noticed that the print statements I've used for debugging get executed only once printing those initial null values, until, I hot-refresh the page and the non-null values are shown.
Is there a way to go around this? I've been stuck for a couple of days now without making any significant advances and any sort of guidance would be appreciated.

How to create a Loop to see how much you scrolled in Flutter?

How can you create a loop where the scrolled amount in pixels get displayed using ScrollPosition
Let's say the user scrolls up and down through the screen where he does the following,
Assume that the view is 10,000 pixels
Goes around halfway in the screen, Come backs to the top and goes somewhere around 3/4 of the view. Can you use ScrollPosition to get create a loop to get the total distance scrolled in pixels, so a number will display as, 5000+5000+7500 = 17500 pixels. Any ideas would be appreciated.
Code:
body: TabBarView(
children: [
new ListView.separated(
separatorBuilder: (context, index) => Divider(),
itemCount: 10,
itemBuilder: (context, index) => ListTile(
leading: CircleAvatar(
backgroundImage: AssetImage("assets/images/avatar.jpg"),
),
title: Text("List Index is $index"),
),
),
])
You could create a state
double totalScrolled = 0;
in your widget definition and wrap your ListView in a NotificationListener<ScrollUpdateNotification>
NotificationListener<ScrollUpdateNotification>(
child: ListView(
),
onNotification: (notif) {
totalScrolled +=notif.scrollDelta.abs();
},
),
then simply add the absolute value of the scrollDelta to your totalScrolled

Reference to an enclosing class method cannot be extracted on refactoring

When I want to do "Extract to widget", it raises an error : "reference to an enclosing class method cannot be extracted"
I know there is some variables that must get their data from class constructor but I want Android studio to extract the widget then, I will correct the mistaken codes, like Visual Studio that without any error extract the code to a new widget then it needs to copy the new extracted widget to a new dart file and correct the mistakes.
I want to extract the Card widget part.
import 'package:flutter/material.dart';
import 'package:flutter/material.dart' as prefix0;
import 'package:intl/intl.dart';
import '../model/transaction.dart';
class TransactionList extends StatelessWidget {
final List<Transaction> transactions;
final Function deleteTx;
TransactionList(this.transactions, this.deleteTx);
#override
Widget build(BuildContext context) {
return transactions.isEmpty
? LayoutBuilder(
builder: (ctx, constraint) {
return Column(
children: <Widget>[
Text(
'There is no transaction',
style: Theme.of(context).textTheme.title,
textDirection: prefix0.TextDirection.rtl,
),
SizedBox(
height: 10,
),
Container(
height: constraint.maxHeight * 0.6,
child: Image.asset(
'assets/images/yalda.png',
fit: BoxFit.cover,
))
],
);
},
)
: ListView.builder(
itemCount: transactions.length,
itemBuilder: (ctx, index) {
return **Card**(
margin: const EdgeInsets.symmetric(vertical: 8, horizontal: 5),
elevation: 5,
child: ListTile(
leading: CircleAvatar(
radius: 30,
child: Padding(
padding: const EdgeInsets.all(8),
child: FittedBox(
child: Text('\$${transactions[index].amount}')),
),
),
title: Text(
transactions[index].title,
style: Theme.of(context).textTheme.title,
),
subtitle: Text(DateFormat.yMMMd()
.format(transactions[index].date)
.toString()),
trailing: MediaQuery.of(context).size.width > 360
? FlatButton.icon(
onPressed: () => deleteTx(transactions[index].id),
icon: const Icon(Icons.delete),
label: const Text('Delete'),
textColor: Theme.of(context).errorColor,
)
: IconButton(
icon: const Icon(Icons.delete),
color: Theme.of(context).errorColor,
onPressed: () => deleteTx(transactions[index].id),
),
),
);
});
}
}
Simply use "Extract Method" instead of "Extract Widget". VSCode will add all the returns and references.
Edit: If you want to use "Extract Widget" only then simply wrap that widget in a Container and then use "Extract Widget" on that widget.
If that doesn't work, comment out setState() function inside the widget and try again.
Your deleteTx might contain a setState(() {}) method, try to comment that part of your code where you're calling deleteTx it and just put it back after your extraction.
Just remove or comment the setState() {} from your widget and it gonna works.
transform onpressed etc. property to comments and then try again 'Extract Widget' and go on
I had the same issue and in my case it was because of ListView.builder as yours.
So it is easy to fix, Simply make a class and return Card in Widget build and return it in ListView.builder inside the TransactionList class with the desired arguments.
You have to care about a few things:
Whenever you are extracting a widget, that widget should not contain any variable which is used in the current working page.
All the functions or methods should be parametric.
It is because you are referencing a variable(for example, transactions) in your Card widget from the enclosing class i.e. TransactionList. The best way to extract in this case could be to just make stateless/stateful widget outside your class and cut the Card widget and paste it as the return type of the build method of that Widget you created. And you can reference those variables using the constructor of that widget you created.
If you can comment out deleteTx(transactions[index].id) parts of your code and then use onPressed: (){}, you will be able to extract to widget.
After the extraction you can use:
onPressed: (){
setState(() {
deleteTx(transactions[index].id);
});
}
there might be some local referance to the variable in the current class,so if there some referance we cant extract a widget from there.
You can use the "Extract Method". It's a simple way. VSCode will add all the returns and references.
If we extract the widget it will StatelessWidget. StatelessWidget doesn't support changing state so if you use any Onchange property SetState it never extract
so please remove setState(() {});
Just remove or comment the setState() {} from your widget
Or
Just remove or comment the MediaQuery.of(context).size.width from your widget

How to change the color of text dynamically in Flutter?

This is a small section of my app
I made this by hard coding every detail. But I want to achieve this dynamically.
The problem is - if I code for a certain period of time, I'll input the number of hours I've coded and if I achieve my milestone the milestone text must become red and the next milestone must become green.
This is my code so far
My List Of Milestones
List milestoneList = [10, 25, 50, 100, 250, 500, 750, 1000];
Mapping Lists to Widgets
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: milestoneList.map((item) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
item.toString(),
style: TextStyle(
fontWeight: FontWeight.bold,),
),
);
}).toList(),
So far, I'm able to crack that I need to pass value to color argument of TextStyle. I couldn't think beyond this point.
Edit: if you want different color for every item, u can use function like this,
Color getColor(number) {
if (number > 0 && number < 100) return Colors.red;
if (number >= 100 && number < 200) return Colors.blue;
...
}
And update color property,
color: getColor(item),
Add this below milestoneList
Color textColor = Colors.black; // Default color
Change your textstyle like that,
TextStyle(
fontWeight: FontWeight.bold,
color: textColor,
),
And do this for update color,
FlatButton(
onPressed: () {
setState(() => textColor =
Color((Random().nextDouble() * 0xFFFFFF).toInt() << 0)
.withOpacity(1.0)); // this is generate random color, u can use your own..
},
child: Text("change color"),
),
If I understand you clear
You can do this use if condition in map
code snippet
milestoneList.map((item) {
if (item > 100) {
full code
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
List milestoneList = [];
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
#override
void initState() {
// TODO: implement initState
super.initState();
milestoneList = [10, 25, 50, 100, 250, 500, 750, 1000];
}
#override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Invoke "debug painting" (press "p" in the console, choose the
// "Toggle Debug Paint" action from the Flutter Inspector in Android
// Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
// to see the wireframe for each widget.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: milestoneList.map((item) {
if (item > 100) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
item.toString(),
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
);
}
if (item < 100) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
item.toString(),
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.red,
),
),
);
}
if (item == 100) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
item.toString(),
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.green,
),
),
);
}
}).toList()),
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.display1,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}

Categories

Resources