setState() or markNeedsBuild() called during build - android-studio

I am trying to launch a 'How to Use' screen if the app launches for the first time using a FutureBuilder().
isFisrtTime() returns true if the app is launched for the first time.
Here is the FutureBuilder code:
body: FutureBuilder(
future: isFirstTime(),
initialData: Container(),
builder: (context, snapshot){
if(snapshot.data == true){
Navigator.pushNamed(context, '/howtouse');
return (currentScreen == 'Home')
? HomeBody()
: CBook(changeScreen: changeCurrentScreen);
}
else {
return (currentScreen == 'Home')
? HomeBody()
: CBook(changeScreen: changeCurrentScreen);
}
},
),
Please Help

You can't push a new Route during build. Move the logic to your initState instead.
#override
void initState() {
isFirstTime().then((data) {
if(data) {
Navigator.pushNamed(context, '/howtouse');
}
}
super.initState();
}

Related

How to add or delete item from a list in MongoDB without refreshing the frontend

guys
I'm trying to Create a list to add and delete items which are already stored in my database(MongoDB). I already created an Animated list that once its clicked on it deletes the item but as soon as i add the function to remove the item from the database it begins to show a loader as I used a ListView.builder
FutureBuilder(
builder: (context, projectSnap) {
if (projectSnap.connectionState == ConnectionState.none &&
projectSnap.hasData == null) {
//print('project snapshot data is: ${projectSnap.data}');
return Text('Nothing To show Here');
}else if(
projectSnap.connectionState == ConnectionState.waiting
) {
return Center(child: CircularProgressIndicator());
} else if(
projectSnap.connectionState == ConnectionState.active
) {
return AnimatedList(
padding: EdgeInsets.zero,
key: _listKey,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
initialItemCount: pairlenght,
itemBuilder: (BuildContext context, int index, animation) {
pair1 = pair[index].split(" ")[1];
pair0 = pair[index].split(" ")[0];
return _buildItem(pair, index, animation);
}
);
}
else{
return AnimatedList(
padding: EdgeInsets.zero,
key: _listKey,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
initialItemCount: pairlenght,
itemBuilder: (BuildContext context, int index, animation) {
pair1 = pair[index].split(" ")[1];
pair0 = pair[index].split(" ")[0];
return _buildItem(pair, index, animation);
}
);
}
},
future: fetchlist(),
),
This is the code for the list
void _removeSingleItems(int index) {
int removeIndex = index;
String removedItem = pair.removeAt(removeIndex);
// This builder is just so that the animation has something
// to work with before it disappears from view since the original
// has already been deleted.
AnimatedListRemovedItemBuilder builder = (context, animation) {
// A method to build the Card widget.
return _buildItem(removedItem, index, animation);
};
_listKey.currentState?.removeItem(removeIndex, builder);
}
This is the code to delete the item using Animated list
This is the how it works
So what I actually need is just a better alternative if listview.builder won't do the job.
Cause whenever I delete it loads again which I don't want

how to stream user query instead of awaiting it

so, I am using a search Delegate to allow user search, however, when user is inputting data, it's really slow, it lags and skips frames as they type, because my search delegate loops through a list that contains about 2000 objects and checks user query against each object's title and main text, making about 4000 search criteria in total, this was causing the lag, so to solve this, I simply spawned a new isolate to handle the suggestions building, as such
Future<List<Data>> allData(String query) async {
List<Data> suggestions;
List<Data> myData(String message) {
return allData.where((data) {
final result = data.dataText.replaceAll(RegExp('[^A-Za-z0-9 ]'), '').toLowerCase();
final result1 = data.dataText.replaceAll(RegExp('[^A-Za-z0-9]'), '').toLowerCase();
final result2 = data.dataText.toLowerCase();
final result3 = data.dataText.replaceAll("th'incarnate", "the incarnate").toLowerCase();
final input = query.toLowerCase();
final result4 = data.dataTitle.toLowerCase();
final result5 = data.dataTitle.replaceAll(RegExp('[^A-Za-z0-9 ]'), '').toLowerCase();
final result6 =data.dataTitle.replaceAll(RegExp('[^A-Za-z0-9]'), '').toLowerCase();
return result.contains(input) ||
result1.contains(input) ||
result2.contains(input) ||
result3.contains(input) ||
result4.contains(input) ||
result5.contains(input) ||
result6.contains(input);
}).toList();
}
query.isEmpty
? suggestions = []
: suggestions = await compute<String, List<Data>>(
myData,
"",
);
if (query.isNotEmpty && suggestions.isEmpty) {
return [
Data(
DataTitle: "No Results",
DataText: "No Results",
),
];
} else {
return suggestions;
}
}
then in buildSuggestions I did this
#override
Widget buildSuggestions(BuildContext context) {
return FutureBuilder<List<Data>>(
future: DataBrain().allData(query),
builder: (context, snapshot) {
List<Data> suggestions;
query.isEmpty ? suggestions = [] : suggestions = snapshot.data!;
return ListView.builder(
itemCount: suggestions.length,
itemBuilder: (context, suggestIndex) {
final suggestion = suggestions[suggestIndex];
if (snapshot.connectionState == ConnectionState.done) {
if (suggestion.dataText.toLowerCase() == "no results") {
return const ListTile(
title: Text("No results"),
subtitle: Text("Please check for spelling mistakes"),
);
} else {
return ListTile(
title: Text(suggestion.dataTitle),
trailing: Text(
'${suggestion.dataBookName} ${suggestion.dataBookNumber == 0 ? '30a' : suggestion.dataBookNumber}',
style: const TextStyle(
fontStyle: FontStyle.italic,
fontWeight: FontWeight.w300,
),
),
onTap: () {
close(context, suggestion);
},
);
}
} else {
return const ListTile(
title: Text('Loading...'),
);
}
});
});
}
it works fine without lags, however now, the suggestions don't build as user types, it waits till user is no longer typing, for like 2 seconds, then it builds suggestions, I think this is because it's a future and in a future builder, I'm not sure why. I'm thinking of turning it into a stream rather and listen to query change and build immediately instead of awaiting for two seconds after user stops querying, is there any workaround, what's a good way to eliminate the delay, i need it to filter the list and build suggestions with each keystroke, as it used to do before i moved it to a new isolate and made it a future

Dart Map and Parsing JSON

As a new Dart Fan, I would like to understand the concept of Map/List.
I tried to do HTTP requests, getting JSON data. And it's ok till I have to assign to the Map.
Let me show you the example of JSON data:
{
"error":"",
"error_number":"",
"response_code":200,
"result":[
{
"id":1,
"name":"Great Deal",
"day_aired":"2015-07-05 11:06:09",
"trend":"Noone",
"trend_details": [{
"name":"Great Deal",
}
]
},
{
"id":2,
....
}
]
}
The code:
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<ApiResponse> fetchData(String command, Map params) async {
final String url =
'https://example.com/api/v2/....';
final response = await http.get(url);
if (response.statusCode == 200) {
return ApiResponse.fromJson(json.decode(response.body));
} else {
// If that call was not successful, throw an error.
throw Exception('Failed to load post');
}
}
}
final response = await http.get(url);
dynamic data = json.decode(response.body);
List<String> parsed = data['result'] as List<String>;
// List<String> parsedList = new List<String>.from(parsed);
if (response.statusCode == 200) {
//return json.decode(response.body);
List<ApiResponse> list = List<ApiResponse>.from(
parsed.map((i) => ApiResponse.fromJson(i as Map<String, dynamic>)));
}
I do the same as this article. But I read this article too. I'm trying to create Future<ApiResponse> with data from json.decode(response.body) (result entry inside of it).
factory ApiResponse.fromJson(Map<String, dynamic> json) {...}
But as I understand, result is not Map<String, dynamic> but when I try to invoke the code below it says:
Unhandled Exception: type 'List<dynamic>' is not a subtype of type 'List<String>' in type cast and it referred to List<String> parsed = data['result'] as List<String>;.
I'm confused and I know the code is a mess. I read in the second article that I should do an additional cast to trend_details but it did not work as I expected. Obviously data['result'] is an array but how to cast it properly? What are the good practices?
result stores a list of Map<String, dynamic>
final parsed = data['result'] as List<Map<String, dynamic>>;
You can parse json string with the following structure and code
You can see picture display correct result name
code snippet
var payload = payloadFromJson(jsonStr);
print('${payload.result[0].name}');
related class
// To parse this JSON data, do
//
// final payload = payloadFromJson(jsonString);
import 'dart:convert';
Payload payloadFromJson(String str) => Payload.fromJson(json.decode(str));
String payloadToJson(Payload data) => json.encode(data.toJson());
class Payload {
String error;
String errorNumber;
int responseCode;
List<Result> result;
Payload({
this.error,
this.errorNumber,
this.responseCode,
this.result,
});
factory Payload.fromJson(Map<String, dynamic> json) => Payload(
error: json["error"] == null ? null : json["error"],
errorNumber: json["error_number"] == null ? null : json["error_number"],
responseCode: json["response_code"] == null ? null : json["response_code"],
result: json["result"] == null ? null : List<Result>.from(json["result"].map((x) => Result.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"error": error == null ? null : error,
"error_number": errorNumber == null ? null : errorNumber,
"response_code": responseCode == null ? null : responseCode,
"result": result == null ? null : List<dynamic>.from(result.map((x) => x.toJson())),
};
}
class Result {
int id;
String name;
DateTime dayAired;
String trend;
List<TrendDetail> trendDetails;
Result({
this.id,
this.name,
this.dayAired,
this.trend,
this.trendDetails,
});
factory Result.fromJson(Map<String, dynamic> json) => Result(
id: json["id"] == null ? null : json["id"],
name: json["name"] == null ? null : json["name"],
dayAired: json["day_aired"] == null ? null : DateTime.parse(json["day_aired"]),
trend: json["trend"] == null ? null : json["trend"],
trendDetails: json["trend_details"] == null ? null : List<TrendDetail>.from(json["trend_details"].map((x) => TrendDetail.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"id": id == null ? null : id,
"name": name == null ? null : name,
"day_aired": dayAired == null ? null : dayAired.toIso8601String(),
"trend": trend == null ? null : trend,
"trend_details": trendDetails == null ? null : List<dynamic>.from(trendDetails.map((x) => x.toJson())),
};
}
class TrendDetail {
String name;
TrendDetail({
this.name,
});
factory TrendDetail.fromJson(Map<String, dynamic> json) => TrendDetail(
name: json["name"] == null ? null : json["name"],
);
Map<String, dynamic> toJson() => {
"name": name == null ? null : name,
};
}
full code
import 'package:flutter/material.dart';
// To parse this JSON data, do
//
// final payload = payloadFromJson(jsonString);
import 'dart:convert';
Payload payloadFromJson(String str) => Payload.fromJson(json.decode(str));
String payloadToJson(Payload data) => json.encode(data.toJson());
class Payload {
String error;
String errorNumber;
int responseCode;
List<Result> result;
Payload({
this.error,
this.errorNumber,
this.responseCode,
this.result,
});
factory Payload.fromJson(Map<String, dynamic> json) => Payload(
error: json["error"] == null ? null : json["error"],
errorNumber: json["error_number"] == null ? null : json["error_number"],
responseCode: json["response_code"] == null ? null : json["response_code"],
result: json["result"] == null ? null : List<Result>.from(json["result"].map((x) => Result.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"error": error == null ? null : error,
"error_number": errorNumber == null ? null : errorNumber,
"response_code": responseCode == null ? null : responseCode,
"result": result == null ? null : List<dynamic>.from(result.map((x) => x.toJson())),
};
}
class Result {
int id;
String name;
DateTime dayAired;
String trend;
List<TrendDetail> trendDetails;
Result({
this.id,
this.name,
this.dayAired,
this.trend,
this.trendDetails,
});
factory Result.fromJson(Map<String, dynamic> json) => Result(
id: json["id"] == null ? null : json["id"],
name: json["name"] == null ? null : json["name"],
dayAired: json["day_aired"] == null ? null : DateTime.parse(json["day_aired"]),
trend: json["trend"] == null ? null : json["trend"],
trendDetails: json["trend_details"] == null ? null : List<TrendDetail>.from(json["trend_details"].map((x) => TrendDetail.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"id": id == null ? null : id,
"name": name == null ? null : name,
"day_aired": dayAired == null ? null : dayAired.toIso8601String(),
"trend": trend == null ? null : trend,
"trend_details": trendDetails == null ? null : List<dynamic>.from(trendDetails.map((x) => x.toJson())),
};
}
class TrendDetail {
String name;
TrendDetail({
this.name,
});
factory TrendDetail.fromJson(Map<String, dynamic> json) => TrendDetail(
name: json["name"] == null ? null : json["name"],
);
Map<String, dynamic> toJson() => {
"name": name == null ? null : name,
};
}
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;
String jsonStr = '''
{
"error":"",
"error_number":"",
"response_code":200,
"result":[
{
"id":1,
"name":"Great Deal",
"day_aired":"2015-07-05 11:06:09",
"trend":"Noone",
"trend_details": [{
"name":"Great Deal"
}
]
}
]
}
''';
void _incrementCounter() {
var payload = payloadFromJson(jsonStr);
print('${payload.result[0].name}');
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
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>[
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.
);
}
}

How to setState() on a ModalRoute?

I write an app in flutter. I want to change the state of a string variable. After I set the state of the string variable, the ModalRoute PopUpMenu does not show the changed variable. If I close the ModalRoute PopUpMenu and open it again, I can see the changed variable.
I tried to pop the context, but I want the change on the PopUpMenu. I've got my own Overlay widget.
class MyOverlay extends ModalRoute {
...
}
// this is my main.dart:
List<String> categories = ['please', 'help', 'me'];
String _selectedCategory = 'category';
// this is where the PopUpMenu starts
floatingActionButton: FloatingActionButton(
child: ...,
onPressed: () {
_showPopup(context, _popupBody(), 'Add');
},
),
_showPopup(BuildContext context, Widget widget, String title, {BuildContext popupContext}) {
Navigator.push(
context,
MyOverlay(
...
onPressed: () {
try {
Navigator.pop(context); //close the popup
} catch (e) {
print(e);
}
},
...
body: widget,
) ...
);
}
Widget _popupBody() {
...
PopupMenuButton<String>(
// HERE IS THE PROBLEM THIS SHOULD CHANGE WHEN I SELECT
child: Text('$_selectedCategory'),
itemBuilder: (BuildContext context) {
return categories.map((String choice) {
return PopupMenuItem<String>(
value: choice,
child: Text(choice),
);
}).toList();
},
onSelected: _selectCategory,
),
...
}
void _selectCategory(String category) {
setState(() => this._selectedCategory = category);
}
The text widget does not change if I select the PopupMenuItem.
I have the same problem, I fixed for the moment using changedExternalState (); to force a rebuild, but i think maybe this isn't optimal.
Example:
CheckboxListTile(
value: _checkboxValue,
title: Text(phone),
onChanged: (value){
_checkboxValue = value;
//Fix
changedExternalState();
},
)

Reuse spinner in sync methods without repeating code

In our code, we repeat the same sequence multiple times: starting a spinner, then execute a spawnSync method and update the spinner depending on result. For example here is one of the method:
cloneSync() {
const spinner = ora({
text: 'Cloning repository',
color: 'cyan',
spinner: 'arrow3'
}).start();
let clone = spawnSync('git', ['clone', repository.url, repository.name]);
if (clone.stderr) {
spinner.fail('Error while cloning repository');
throw new Error(clone.stderr);
} else {
spinner.succeed('Successfully cloned repository');
return clone.stdout;
}
}
Another code example so you can see the logic is almost identical:
parseLatestTagAndTransmitToDocker() {
const spinner = ora({
text: 'Checking latest tag',
color: 'cyan',
spinner: 'arrow3'
}).start();
let tag = spawnSync('git', ['describe', '--abbrev=0']);
if (tag.stderr) {
spinner.fail('Error while fetching latest tag of repository');
throw new Error(tag.stderr);
} else {
spinner.text(`Successfully retrieved latest tag: ${tag.stdout}`);
let docker = spawnSync('docker', ['run', 'myimage:latest', tag.stdout]);
if (docker.stderr) {
spinner.fail('Error while transmitting tag to docker image');
throw new Error(docker.stderr)
} else {
spinner.success('Successfully transmitted tag to docker service');
return docker.stdout;
}
}
}
Is it possible, in node 8+ to wrap this code and make it more reusable. I struggle finding a reusable code without having to trigger spinner and the if/else condition. Doing so with async allow use of try/catch and await/async. But here with sync method, I don't find the proper way to code that kind of behavior.
From the two examples you've provided, I can see a "SpinnerSpawner" function that returns a promise:
function spinnerSpawner(spinnerConfig, cmd, args) {
if (typeof(spinnerConfig) == "string") spinnerConfig = {
text: spinnerConfig,
color: "cyan",
spinner: "arrow3"
}
return new Promise(function(resolve, reject) {
let spinner = ora(spinnerConfig).start,
tag = spawnSync(cmd, args)
if (!tag.stdError) {
resolve(spinner, tag)
} else {
reject(spinner, tag)
}
})
}
cloneSync() {
spinnerSpawner("cloning repository", "git", ["clone", repository.url, repository.name])
.then(function(spinner, proc) {
spinner.succeed('Successfully cloned repository');
return proc.stdout;
}, function(spinner, proc) {
spinner.fail('Error while cloning repository');
throw new Error(proc.stderr);
}
)
}
parseLatestTagAndTransmitToDocker() {
spinnerSpawner("Checking latest tag", "git", ["describe", "--abbrev=0"])
.then(
function(spinner, proc) {
spinner.text(`successfully retrieved latest tag: ${proc.stdout}`)
return spinnerSpawner("checking docker", "docker", ["run", "myimage:latest", proc.stdout])
}
).then(
function(spinner, proc) {
spinner.success("Processing completed")
return proc.stdout
},
function(spinner, proc) {
spinner.fail(`processing error: ${proc.stderr}`);
throw new Error(tag.stderr);
}
)
}
as always, my code is pseudo-code and not fit for execution - let alone production!

Resources