I need to change application locale programmatically when button pressed
my code :
MaterialApp(
localizationsDelegates: [
_newLocaleDelegate,
const AppTranslationsDelegate(),
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: [
const Locale('en', ''),
const Locale('ar', ''),
],
locale: mylocale,
title: appTitle,
home: Scaffold(
body: new MyCustomForm(),
),
debugShowCheckedModeBanner: false
);
new MaterialButton(
onPressed: () {
setState(() {
mylocale=Locale("ar","");
_newLocaleDelegate = AppTranslationsDelegate(newLocale:mylocale);
});
},
),
translation code :
AppTranslations.of(context).text("text")
AppTranslations Class:
class AppTranslations {
Locale locale;
static Map<dynamic, dynamic> _localisedValues;
AppTranslations(Locale locale) {
this.locale = locale;
_localisedValues = null;
}
static AppTranslations of(BuildContext context) {
return Localizations.of<AppTranslations>(context, AppTranslations);
}
static Future<AppTranslations> load(Locale locale) async {
AppTranslations appTranslations = AppTranslations(locale);
String jsonContent =
await rootBundle.loadString("assets/locale/localization_${locale.languageCode}.json");
_localisedValues = json.decode(jsonContent);
return appTranslations;
}
get currentLanguage => locale.languageCode;
String text(String key) {
print(key);
if(_localisedValues!=null)
return _localisedValues[key] ?? "$key";
else
return key;
}
}
my problem :
when Locale change page direction changed without translation ,
to get effect translation need to refresh page or go to another page and return back,
any help
Could you try wrapping your MaterialApp in an AnimatedSwitcher like so:
AnimatedSwitcher(
// Following two fields for your reference
// duration: const Duration(milliseconds: 500),
// transitionBuilder: (Widget child, Animation<double> animation) {
// return ScaleTransition(child: child, scale: animation);
// },
child: MaterialApp(
// As before, the same code, however:
key: ValueKey<Locale>(mylocale),
)
)
BTW, good practice to prefix private variables with an underscore, e.g. _myLocale.
Related
If you make a new Flutter project and include the dependencies and then replace your main.dart file you should be where I am on this question.
I left the original load: with Future.delayed but it doesn't seem to matter. I know partially what my problem is but am unable to come up with a better solution.
1) I don't seem to be using my snapshot.data and instead I am just making a empty List with str and then i just addAll into it and use that. So i'd love to not do that, i originally was using snapshot.data but ran into problems when I tried to "pull to load more data" which happens after you scroll to the bottom of the list.
The problem with my current method of doing this is that if you pull to load more users and then try to pull again before the users have loaded, The app breaks and doesn't wait for the data to properly load. I believe that I need to be doing that all in the load: of this library easy_refresh... but I am not sure how to rewrite my code to accomplish that.
How can I get my data to load with snapshot.data and then when I pull to refresh, I append 100 more users to that list but the UI waits for the list to update before it finishes the load. Would I be better off just putting a Blocking UI element and after the str list updates? and when new users are loaded I unblock the UI? which sorta feels hackish and not the correct way to solve this. The plugin itself should be able to do the loading and when its ready it stops the spinner under the list and says "finished".
pubspec.yaml
dependencies:
flutter:
sdk: flutter
flutter_easyrefresh: ^1.2.7
http: ^0.12.0+2
main.dart
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:convert';
import 'package:flutter_easyrefresh/easy_refresh.dart';
import 'package:http/http.dart' as http;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
backgroundColor: Colors.white
),
home: DuelLeaderBoards(),
);
}
}
class DuelLeaderBoards extends StatefulWidget {
#override
_DuelLeaderBoardsState createState() => _DuelLeaderBoardsState();
}
class _DuelLeaderBoardsState extends State<DuelLeaderBoards> {
List<Entry> str = [];
GlobalKey<EasyRefreshState> _easyRefreshKey = new GlobalKey<EasyRefreshState>();
GlobalKey<RefreshHeaderState> _headerKey = new GlobalKey<RefreshHeaderState>();
GlobalKey<RefreshHeaderState> _connectorHeaderKey = new GlobalKey<RefreshHeaderState>();
GlobalKey<RefreshFooterState> _footerKey = new GlobalKey<RefreshFooterState>();
GlobalKey<RefreshFooterState> _connectorFooterKey = new GlobalKey<RefreshFooterState>();
Future<LeaderBoards> getLeaderBoards(start) async {
String apiURL = 'https://stats.quake.com/api/v2/Leaderboard?from=$start&board=duel&season=current';
final response = await http.get(apiURL);
if (response.statusCode == 200) {
final responseBody = leaderBoardsFromJson(response.body);
return responseBody;
} else {
throw Exception('Failed to load Data');
}
}
void updateLeaderBoardList(e) async {
setState(() {
str.addAll(e.entries);
});
}
#override
void initState() {
getLeaderBoards(0).then((onValue) => str = onValue.entries );
super.initState();
}
#override
Widget build(BuildContext context) {
Widget header = ClassicsHeader(
key: _headerKey,
refreshText: "pullToRefresh",
refreshReadyText: "releaseToRefresh",
refreshingText: "refreshing...",
refreshedText: "refreshed",
moreInfo: "updateAt",
bgColor: Colors.transparent,
textColor: Colors.white,
);
Widget footer = ClassicsFooter(
key: _footerKey,
loadHeight: 50.0,
loadText: "pushToLoad",
loadReadyText: "releaseToLoad",
loadingText: "loading",
loadedText: "loaded",
noMoreText: "Finished",
moreInfo: "updateAt",
bgColor: Colors.transparent,
textColor: Colors.white,
);
return FutureBuilder(
future: getLeaderBoards(0),
builder:
(BuildContext context, AsyncSnapshot<LeaderBoards> snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(),
);
} else {
return Builder(builder: (BuildContext context) {
return Center(
child: new EasyRefresh(
key: _easyRefreshKey,
behavior: ScrollOverBehavior(),
refreshHeader: ConnectorHeader(
key: _connectorHeaderKey,
header: header,
),
refreshFooter: ConnectorFooter(
key: _connectorFooterKey,
footer: footer,
),
child: CustomScrollView(
semanticChildCount: str.length,
slivers: <Widget>[
SliverList(
delegate: SliverChildListDelegate(<Widget>[header]),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return new Container(
height: 70.0,
child: Card(
child: new Text(
'${index+1}: ${str[index].userName}',
style: new TextStyle(fontSize: 18.0),
),
));
},
childCount: str.length,
)),
SliverList(
delegate: SliverChildListDelegate(<Widget>[footer]),
)
],
),
onRefresh: () async {
await new Future.delayed(const Duration(seconds: 0), () {
setState(() {});
});
},
loadMore: () async {
getLeaderBoards(str.length).then((onValue) => {
updateLeaderBoardList(onValue)
});
},
// loadMore: () async {
// await new Future.delayed(const Duration(seconds: 0), () {
// getLeaderBoards(str.length).then((onValue) => {
// updateLeaderBoardList(onValue)
// });
// });
// },
)
);
});
}
});
}
}
LeaderBoards leaderBoardsFromJson(String str) {
final jsonData = json.decode(str);
return LeaderBoards.fromJson(jsonData);
}
String leaderBoardsToJson(LeaderBoards data) {
final dyn = data.toJson();
return json.encode(dyn);
}
class LeaderBoards {
String boardType;
List<Entry> entries;
int totalEntries;
LeaderBoards({
this.boardType,
this.entries,
this.totalEntries,
});
factory LeaderBoards.fromJson(Map<String, dynamic> json) => new LeaderBoards(
boardType: json["boardType"] == null ? null : json["boardType"],
entries: json["entries"] == null ? null : new List<Entry>.from(json["entries"].map((x) => Entry.fromJson(x))),
totalEntries: json["totalEntries"] == null ? null : json["totalEntries"],
);
Map<String, dynamic> toJson() => {
"boardType": boardType == null ? null : boardType,
"entries": entries == null ? null : new List<dynamic>.from(entries.map((x) => x.toJson())),
"totalEntries": totalEntries == null ? null : totalEntries,
};
}
class Entry {
String userName;
int eloRating;
String profileIconId;
String namePlateId;
Entry({
this.userName,
this.eloRating,
this.profileIconId,
this.namePlateId,
});
factory Entry.fromJson(Map<String, dynamic> json) => new Entry(
userName: json["userName"] == null ? null : json["userName"],
eloRating: json["eloRating"] == null ? null : json["eloRating"],
profileIconId: json["profileIconId"] == null ? null : json["profileIconId"],
namePlateId: json["namePlateId"] == null ? null : json["namePlateId"],
);
Map<String, dynamic> toJson() => {
"userName": userName == null ? null : userName,
"eloRating": eloRating == null ? null : eloRating,
"profileIconId": profileIconId == null ? null : profileIconId,
"namePlateId": namePlateId == null ? null : namePlateId,
};
}
I looked at the documentation of loadMore. Since it says that the body of the function assigned to loadMore should be async, you do not need to use then:
loadMore: () async {
final result = await getLeaderBoards(str.length);
updateLeaderboardList(result);
},
loadMore: () async {
await getLeaderBoards(str.length).then((onValue) => {
updateLeaderboardList(onValue)
});
},
but putting "await" my loader waits for the function to complete before it finishes the animation.
I'm trying to fill a ListView with account information, which gets fetched from a Sqflite database. Here is how I read the account data from db:
dbhelper.dart
Future<List<AccountModel>> getAllAccounts() async {
final dbClient = await db;
print('getAllAccounts');
List<Map> res = await dbClient.rawQuery('SELECT * FROM Accounts');
if (res.isNotEmpty) {
print('Accounts $res');
var accounts = [];
for (var i = 0; i < res.length; i++) {
var account =AccountModel.fromDb(res[i]);
print('Account $account');
accounts.add(account);
}
print('Return $accounts');
return accounts;
}
return [];
This is, how I try to contruct the list view, using the FutureBuilder-Widget:
account_list_widget.dart
class AccountList extends StatefulWidget {
State<StatefulWidget> createState() {
return AccountListState();
}
}
class AccountListState extends State<AccountList> {
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
FutureBuilder<List<AccountModel>>(
future: DbProvider().getAllAccounts(),
builder: (BuildContext context, AsyncSnapshot<List<AccountModel>> snapshot) {
if (snapshot.hasData) {
return Stack(
children: <Widget>[
ListView.builder(
itemCount: snapshot.data.length + 1,
itemBuilder: (BuildContext context, int index) {
if (index == 0) {
return ListSectionHeader('Konten');
} else {
// Korrigieren des Index, da ja die 0 schon "verbraucht" wurde,
// auf die Daten aber weiterhhin mit Start-Index 0 zugegriffen wird.
index--;
AccountModel account = snapshot.data[index];
return Dismissible(
key: UniqueKey(),
background: Container(color: Colors.red),
onDismissed: (direction) {
DbProvider().removeAccount(account.uuid);
},
child: ListTile(
title: Text(account.accountName),
leading: Icon(Icons.crop_square),
trailing: Icon(Icons.keyboard_arrow_right),
),
);
}
},
),
addAccountTile()
],
);
} else {
return ListView.builder(
itemCount: 2,
itemBuilder: (BuildContext context, int index) {
if (index == 0) {
return ListSectionHeader('Konten2');
} else {
return addAccountTile();
}
},
);
}
},
),
],
);
}
Widget addAccountTile() {
return ListTile(
title: Text('Neues Konto hinzufügen'),
leading: Icon(
Icons.add_circle,
color: Colors.blue,
),
trailing: Icon(Icons.keyboard_arrow_right),
onTap: () {
Navigator.pushNamed(context, '/newaccount');
}
);
}
}
The custom data model is defined like this:
account_model.dart
class AccountModel {
final String uuid;
final String accountName;
final String host;
final String userName;
final String password;
final String lastConnected;
final bool active;
final bool useHeadset;
AccountModel(this.uuid, this.accountName, this.host, this.userName,
this.password, this.lastConnected, this.active, this.useHeadset);
AccountModel.fromJson(Map<String, dynamic> parsedJson)
: uuid = parsedJson['uuid'],
accountName = parsedJson['accountName'],
host = parsedJson['host'],
userName = parsedJson['userName'],
password = parsedJson['password'],
lastConnected = parsedJson['lastConnected'],
active = parsedJson['active'],
useHeadset = parsedJson['useHeadset'];
AccountModel.fromDb(Map<String, dynamic> parsedJson)
: uuid = parsedJson['uuid'],
accountName = parsedJson['accountName'],
host = parsedJson['host'],
userName = parsedJson['userName'],
password = parsedJson['password'],
lastConnected = parsedJson['lastConnected'],
active = parsedJson['active'] == 1,
useHeadset = parsedJson['useHeadset'] == 1;
Map<String, dynamic> toMap() {
return <String, dynamic> {
'uuid': uuid,
'accountName': accountName,
'host': host,
'userName': userName,
'password': password,
'lastConnected': lastConnected,
'active': active ? 1 : 0,
'useHeadset': useHeadset ? 1 : 0
};
}
}
My problem is, that although the database stores valid data and also the method getAllAccounts()seems to return valid data, the statement snapshot.hasdatainside the FutureBuilder widget alsways is false.
What goes wrong here?
From the console:
D/Sqflite ( 9179): [Thread[main,5,main]] opened 6 /data/user/0/com.example.dashboard/app_flutter/test.db total open count (6)
D/Sqflite ( 9179): [Thread[Sqflite,5,main]] BEGIN EXCLUSIVE
D/Sqflite ( 9179): [Thread[Sqflite,5,main]] PRAGMA user_version;
D/Sqflite ( 9179): [Thread[Sqflite,5,main]] PRAGMA user_version = 1;
D/Sqflite ( 9179): [Thread[Sqflite,5,main]] COMMIT
I/flutter ( 9179): getAllAccounts
D/Sqflite ( 9179): [Thread[Sqflite,5,main]] SELECT * FROM Accounts
I/flutter ( 9179): Accounts [{uuid: 436f2c10-3260-11e9-fe03-65fdfe3a7bbd, accountName: 12345, host: 12345, userName: 12345, password: 12345, lastConnected: 0, active: 0, useHeadset: 0}]
I/flutter ( 9179): Account Instance of 'AccountModel'
I/flutter ( 9179): Return [Instance of 'AccountModel']
(The last line of this snippet from the console corresponds with the print statement at the end of the "getAllAccounts" method.)
I solved the problem:
In the database provider in the method "getAllAccounts() I had to explicitly declare the type of the accounts object like so:
List<AccountModel> accounts = [];
I used flutter_i18n plugin (Android Studio) to generate i18n.dart(class S) and S.of(context).locale_msg will return the locale string. The main code is shown below.
Language should be changed programmatically by invoking onLocaleChange(locale) when click the button in HomePage. It works well in Android simulator, but won't change language in iOS simulator. Wonder what's wrong with my code?
class _PaperMoonAppState extends State<PaperMoonApp> {
SpecifiedLocalizationDelegate _localeOverrideDelegate;
void onLocaleChange(Locale locale) {
setState(() {
if (appVars.appConfig.changeLanguage(locale)) {
_localeOverrideDelegate = new SpecifiedLocalizationDelegate(locale);
appVars.saveConfig(); //print save config file...
}
});
}
#override
void initState() {
SpecifiedLocalizationDelegate.onLocaleChange = this.onLocaleChange;
appVars.loadConfig().then((AppConfig _config) {
appVars.appConfig = _config;
setState(() {
_localeOverrideDelegate =
new SpecifiedLocalizationDelegate(appVars.appConfig.getLocale());
});
});
_localeOverrideDelegate =
new SpecifiedLocalizationDelegate(Locale('zh', ''));
super.initState();
}
#override
Widget build(BuildContext context) {
print(_localeOverrideDelegate.overriddenLocale);
return MaterialApp(
debugShowCheckedModeBanner: false,
title: "Paper Moon",
color: Colors.blueAccent,
localizationsDelegates: [
_localeOverrideDelegate,
S.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate
],
supportedLocales: const <Locale>[
Locale("ja", ""),
Locale("en", ""),
Locale("zh", ""),
],
localeResolutionCallback:
S.delegate.resolution(fallback: _localeOverrideDelegate.overriddenLocale),
home: HomePage(),
// routes: _buildRoutes(),
);
}
}
Custom LocalizationDelegate:
class SpecifiedLocalizationDelegate
extends LocalizationsDelegate<WidgetsLocalizations> {
//class static vars:
//onLocaleChange should be bind to MaterialApp function containing setState().
static LocaleChangeCallback onLocaleChange;
// for instance
final Locale overriddenLocale;
const SpecifiedLocalizationDelegate(this.overriddenLocale);
#override
bool isSupported(Locale locale) => overriddenLocale != null;
#override
Future<WidgetsLocalizations> load(Locale locale) =>
S.delegate.load(overriddenLocale);
#override
bool shouldReload(SpecifiedLocalizationDelegate old) => true;
}
Based on your code, the only thing that seems to be missing is this:
open ios/Runner/Info.plist and add:
<key>CFBundleLocalizations</key>
<array>
<string>ja</string>
<string>en</string>
<string>zh</string>
</array>
As far I as know, by now (march/2019), flutter doesn't yet add automatically the list of supported languages to this file.
I'm using i18n_extensions, but with the same issue...
What worked for me, was use this:
supportedLocales: const <Locale>[
const Locale('en'),
const Locale('pt'),
],
Instead of this:
supportedLocales: const <Locale>[
const Locale('en', 'US'),
const Locale('pt', 'BR'),
],
And then, my i18n.dart. file i've change from this:
extension Localization on String {
static final _t = Translations.from("en_us", {
passwordInput: {
"en_us": "Password",
"pt_br": "Senha",
},
searchingTitle: {
"en_us": "Scanning for devices...",
"pt_br": "Procurando dispositivos...",
},
...
To this:
extension Localization on String {
static final _t = Translations.from("en", {
passwordInput: {
"en": "Password",
"pt": "Senha",
},
searchingTitle: {
"en": "Scanning for devices...",
"pt": "Procurando dispositivos...",
},
It works fine for me.
I have a JSON app calling users but I am having problems dealing with null Maps.
This is the Json file.
MemberInfo.json
{
"Dependents": [
{
"Name": "Kim",
"Relationship": "Parent",
"Entitlements": {
"GP": {
"Entitlement": "10000",
"Utilisation": "500",
"Balance": "9500"
},
"OPS": {
"Entitlement": "10000",
"Utilisation": "500",
"Balance": "9500"
},
"IP": {
"Entitlement": "50000",
"Utilisation": "17000",
"Balance": "33000"
}
}
},
{
"Name": "Tim",
"Relationship": "Spouse",
"Entitlements": {
"GP": {
"Entitlement": "10000",
"Utilisation": "500",
"Balance": "9500"
},
"OPS": {
"Entitlement": "10000",
"Utilisation": "500",
"Balance": "9500"
},
"IP": {
}
}
}
]
}
And the dart file is as follows:
import 'package:flutter/material.dart';
import 'dart:convert';
import 'dart:async';
import 'package:http/http.dart' as http;
final String url = "http://crm.emastpa.com.my/MemberInfo.json";
final String url2 = "http://crm.emastpa.com.my/MemberInfo2.json";
class Dependents extends StatelessWidget {
Dependents({Key key, this.index, this.name, this.page}) : super(key:key);
final int index;
final String name;
final int page;
Future<String> jsonContent() async {
var res = await http.get(
Uri.encodeFull(url2),
headers: {"Accept": "application/json"});
return res.body;
}
#override
Widget build(BuildContext context) {
Widget fifthBody = new Container(
child: new Center(
child: new FutureBuilder<String>(
future: jsonContent(),
builder: (context, snapshot){
if(snapshot.hasData){
List<Widget> widgets = [];
//get snapshot data from JSON tree
var jsondecode = json.decode(snapshot.data);
//[]->Entitlements
var jsonEntData = jsondecode["Dependents"][index]["Entitlements"];
//Everything under Entitlements
var jsonEntDataGP = jsonEntData["GP"];
var jsonEntDataOPS = jsonEntData["OPS"];
var jsonEntDataIP = jsonEntData["IP"];
//GP Branch
var gp_ent = jsonEntDataGP["Entitlement"];
var gp_util = jsonEntDataGP["Utilisation"];
var gp_bal = jsonEntDataGP["Balance"];
//OPS branch
var ops_ent = jsonEntDataOPS["Entitlement"];
var ops_util = jsonEntDataOPS["Utilisation"];
var ops_bal = jsonEntDataOPS["Balance"];
//IP branch
var ip_ent = jsonEntDataIP["Entitlement"];
var ip_util = jsonEntDataIP["Utilisation"];
var ip_bal = jsonEntDataIP["Balance"];
jsonEntDataGP != null?
widgets.add(new ExpansionTile(
title: new Text("GP"),
children: <Widget>[
new ListTile(
title: new Text("Entitlement"),
trailing: new Text(gp_ent),
)
],
))
: new Center();
jsonEntDataOPS != null?
widgets.add(new ExpansionTile(
title: new Text("OPS"),
children: <Widget>[
new ListTile(
title: new Text("Entitlement"),
trailing: new Text(ops_ent),
)
]))
: new Center();
jsonEntDataIP != null?
widgets.add(new ExpansionTile(
title: new Text("IP"),
children: <Widget>[
new ListTile(
title: new Text("Entitlement"),
trailing: new Text(ip_ent),
)
]))
: new Center();
return new Column(
children: widgets,
);
}else if(snapshot.hasError){
return new Text(snapshot.error);
}
//loading the page
return new Center(
child: new CircularProgressIndicator(),
);
}),
),
);
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text("$name + index: $index"),
),
body: fifthBody
),
);
}
}
This is the error I am getting:
I/flutter ( 8842): The following assertion was thrown building FutureBuilder<String>(dirty, state:
I/flutter ( 8842): _FutureBuilderState<String>#f9262):
I/flutter ( 8842): 'package:flutter/src/widgets/text.dart': Failed assertion: line 230 pos 15: 'data != null': is not
I/flutter ( 8842): true.
I am trying to build a list of the user's entitlements but I am not so sure as to how to deal with a null key such as "IP". The app works as long as the key has values inside but will return the exception if the key is null.
How should I deal with this problem?
IP is empty JsonObject
"IP": {},
So when you try to access data of IP object it returns null & passing that to textview later gives data is null error.
You are trying to get data from json object without keys as
var ip_ent = jsonEntDataIP["Entitlement"];
And using later as
new Text(ip_ent)
To overcome this you need to assign default value in case of variable is null or can hide that view as your requirement.
To assign default value in case of null, please do this.
var ip_ent = jsonEntDataIP["Entitlement"] ?? '';
var jsonEntDataIP = jsonEntData["IP"] ?? '';
I have a code like this below, the simple flow is I make a loop from a list of objects to create some widgets.
class ScoringAttribute {
int _id;
bool _isdelete;
double _scorehigh, _scorelow, _scorevalue;
String _name, _scoretype, _description, _title;
}
class HomePageState extends State<HomePage> with TickerProviderStateMixin {
List dataScoringAttributes;
List<ScoringAttribute> listScoringAttributeObjects = new List<ScoringAttribute>();
final String urlPresentation = ".../.resentations/getPresentations";
final String urlScoringAttribute = ".../.scoringattributes/getScoringattributes";
Future<String> getPresentationData() async {
var responseScoringAttribute = await http.get(
Uri.encodeFull(urlScoringAttribute),
headers: {"Accept": "application/json"}
);
var scoringAttributeJson = json.decode(responseScoringAttribute.body);
dataScoringAttributes = scoringAttributeJson['scoringattributes'];
for(int i = 0; i < dataScoringAttributes.length; i++) {
var scoringAttributeObject = new ScoringAttribute();
scoringAttributeObject._id = dataScoringAttributes[i]["id"];
scoringAttributeObject._description = dataScoringAttributes[i]["iddescription"];
scoringAttributeObject._isdelete = dataScoringAttributes[i]["isdelete"];
scoringAttributeObject._name = dataScoringAttributes[i]["name"];
scoringAttributeObject._scorehigh = double.parse(dataScoringAttributes[i]["scorehigh"].toString());
scoringAttributeObject._scorelow = double.parse(dataScoringAttributes[i]["scorelow"].toString());
scoringAttributeObject._scoretype = dataScoringAttributes[i]["scoretype"];
scoringAttributeObject._title = dataScoringAttributes[i]["title"];
scoringAttributeObject._scorevalue = double.parse(dataScoringAttributes[i]["scorelow"].toString());
listScoringAttributeObjects.add(scoringAttributeObject);
}
return "Success";
}
List<Widget> scoringAttributeList() {
List<Widget> list = new List();
for(int i = 0; i < listScoringAttributeObjects.length; i++) {
if(listScoringAttributeObjects[i]._scoretype == "slider") {
list.add(
new Container(
child: new Column(
children: <Widget>[
new Column(
children: <Widget>[
//THE SLIDER VALUE TEXT
new Text(
//CONVERT DOUBLE TYPE TO STRING WITHOUT DECIMAL POINTS
listScoringAttributeObjects[i]._scorevalue.toStringAsFixed(listScoringAttributeObjects[i]._scorevalue.truncateToDouble() == listScoringAttributeObjects[i]._scorevalue ? 0 : 0),
style: new TextStyle(
fontSize: 28.0,
),
),
//THE SLIDER
new Slider(
activeColor: Colors.blueAccent,
inactiveColor: const Color(0xFFb7d2e0),
min: double.parse(listScoringAttributeObjects[i]._scorelow.toString()),
max: double.parse(listScoringAttributeObjects[i]._scorehigh.toString()),
value: double.parse(listScoringAttributeObjects[i]._scorevalue.toString()),
onChanged: (double value) {
setState(() {
listScoringAttributeObjects[i]._scorevalue = double.parse(value.round().toString());
});
},
),
],
),
],
),
),
);
}
else if(listScoringAttributeObjects[i]._scoretype == "text_field") {
list.add(...);
}
else if(listScoringAttributeObjects[i]._scoretype == "stars") {
list.add(...);
}
else if(listScoringAttributeObjects[i]._scoretype == "thumb") {
list.add(new Container(...);
}
}
return list;
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: FutureBuilder<String> (
future: getPresentationData(),
builder: (context, snapshot) {
if(snapshot.hasData) {
return new Column(
children: <Widget>[
new Column(
children: scoringAttributeList(),
),
],
),
}
},
),
);
}
}
There are some different widgets depending on the type, and there are 4 types, and 1 type might have more than 1 widget in it, so I make the loop depend on the data that it got from DB.
The problem is I don't know why every time I use setState() inside the loop, it always processes the loop again, so it'll be an infinite loop to create a new widget, and it'll duplicate the widget from the beginning (only happen when the setState() is called).
Ex: there are 4 data inside the List, and if the setState() is called, it'll show 8 data (show the first 4 data twice)
Here's the example of how I setState() into the data inside the List
onChanged: (double value) {
setState(() {
listScoringAttributeObjects[i]._scorevalue = double.parse(value.round().toString());
});
},
I think the problem is because I setState() into some data inside the List. So when the List state is changed, it'll re-render anything that is related to the List.
Is it true?
If yes, is there any other solution how to change my code?
If not, is there any mistake in my code or my logic maybe?
Thank you. Really looking forward to some solution about this, cause I really got stuck in this, and its already been a week :(
Simple move your getPresentationData() into state variable. So that it will get triggered only once.
class HomePageState extends State<HomePage> with TickerProviderStateMixin {
Future<String> _presentationFuture;
initState() {
_presentationFuture = getPresentationData()
}
//other contents
#override
Widget build(BuildContext context) {
return new Scaffold(
body: FutureBuilder<String> (
future: _presentationFuture,
builder: (context, snapshot) {
if(snapshot.hasData) {
Reason for duplicate: we can calling setState on Slider dataChange which will re-render the HomePageState which will again trigger the network call (getPresentationData())
Note: If you want to trigger network on slider change, clear the list before making a network call
Future<String> getPresentationData() async {
listScoringAttributeObjects = new List<ScoringAttribute>(); // clear data
var responseScoringAttribute = await http.get(
Uri.encodeFull(urlScoringAttribute),
headers: {"Accept": "application/json"}
);
I don't see how this would be related to the one setState() in your code. It is only called when the slider is used.
I think the problem is caused by list.add(...); in scoringAttributeList(). You shouldn't modify data when build() is executed.
You should assume that build() can be called repeatedly and at any time.
Build your code so that this doesn't cause issues when it happens.