Flutter compute method freeze the UI - asynchronous

init
class MyHomePage extends StatefulWidget{
#override
_MyHomePageState createState() => _MyHomePageState();
}
state class :
class _MyHomePageState extends State<MyHomePage> {
int prime;
bool isPrime(int n) {
int count = 0;
for(int i = 1 ; i <= n; ++i) {
if (n % i == 0) {
++count;
}
}
return count == 2;
}
here the writer used ++i instead of i++
/// Returns the nth prime number.
int getnthPrime(int n) {
int currentPrimeCount = 0;
int candidate = 1;
while(currentPrimeCount < n) {
++candidate;
if (isPrime(candidate)) {
++currentPrimeCount;
} }
return candidate;
}
void _getPrime() async {
int ans = await compute(getnthPrime,10);
setState((){
prime=ans;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('Basic AppBar'),
),
body: Column(
children: <Widget> [
Padding(
padding: const EdgeInsets.all(16.0),
child: RaisedButton(child:Text('GEN'), onPressed: _getPrime,)
),
Text('result : $prime'),
]),
),
);
}
}
I have this code found on the net, for tutorial using async compute to make flutter works easier, but the code freeze when I clicked the button.. why ?
the writer add static int on the getinth function but doing so resulting in error compile. so I remove the static keywords
the isPrime like the name suggest should be to check wether a number is PrimeNumbers.
Does it has anything to do with phone models or android version ?? Ive installed the released apped in a 3GB phone

getnthPrime (the function passed to compute(...) needs to be a top-level function.
It must be declared outside of a class.

Related

Null check operator used on a null value in flutter blog app

The following _CastError was thrown building Expanded(flex: 1):
Null check operator used on a null value
The relevant error-causing widget was:
Expanded Expanded:file:///home/abdallah/StudioProjects/gei/lib/news_page.dart:45:13
Expanded(child: GetBuilder<HomeController>(builder:(value) {
if(value.newss.isNotEmpty){
return ListView.builder(
itemCount: value.newss.length,
itemBuilder: (context, index) {
return Padding(padding: const EdgeInsets.symmetric(horizontal: 15.0,vertical: 5.0),
child: Post(model: value.newss[index],),
);
},
);
}else{
return const Center(child: Text("No News Avilable"),);
}
},
),
)
HomeController Code
class HomeController extends GetxController{
final FirebaseFunctions _functions = FirebaseFunctions();
final ScrollController controller = ScrollController();
List<NewsModel> newss = [];
var isLoding = false.obs;
void getData() async{
newss.addAll(await _functions.getNews());
update();
}
#override
void onInit() {
super.onInit();
getData();
controller.addListener(() {
_functions.isLoading.listen((p) {
isLoding.value = p;
});
double maxScrollPoint = controller.position.maxScrollExtent;
double currentPosition = controller.position.pixels;
double height20 = Get.size.height *0.20;
if(maxScrollPoint - currentPosition <= height20){
getData();
}
});
}
}
Try this:
List<HomeController> homecontroller = value.newss[index]
child: Post(model: homecontroller)
I think the error is that because You use the expanded itself did You try to remove it and let me ask did You use it on a row or column

Flutter. My UI is not updated when I receive a value from a Future Function

My problem is that when I receive information from Firestore, I see it in the console that it prints but my UI does not update. But until I press the icon that shows my screen again. The screen where my list of widgets is contained in a BottomNavigationBar.
What I hope will happen with the code is that when I select the tab that will contain my screen in the BottomNavigationBar, the list of Widgets appears with the names of the DocumentIDs. Well, currently I must select the tab again so that they appear.
I attach my code.
class PruebasVarias extends StatefulWidget {
#override
_PruebasVariasState createState() => _PruebasVariasState();
}
class _PruebasVariasState extends State<PruebasVarias> {
List<String> myData = [];
List<Widget> myListWidget = [];
#override
void initState() {
super.initState();
getallDocument();
}
Future getallDocument()async{
final QuerySnapshot result = await Firestore.instance
.collection("Users")
.getDocuments();
final List<DocumentSnapshot> documentos = result.documents;
documentos.forEach((data) {
myData.add(data.documentID);
print(myData);
});
for (int i = 0; i < (myData.length); i++){
myListWidget.add(Text("${myData[i]}"));
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.red,
title: Text("Documents"),
actions: <Widget>[
],
),
body: Center(
child:
Column(children: myListWidget),
)
);
}
}
An easy fix : use then to call a callback function and inside callback function use setState to update the UI.
class PruebasVarias extends StatefulWidget {
#override
_PruebasVariasState createState() => _PruebasVariasState();
}
class _PruebasVariasState extends State<PruebasVarias> {
List<String> myData = [];
List<Widget> myListWidget = [];
#override
void initState() {
super.initState();
getallDocument().then(()=>updateUI()).catchError((error)=>print(error));
}
Future<void> getallDocument()async{
final QuerySnapshot result = await Firestore.instance
.collection("Users")
.getDocuments();
final List<DocumentSnapshot> documentos = result.documents;
documentos.forEach((data) {
myData.add(data.documentID);
print(myData);
});
for (int i = 0; i < (myData.length); i++){
myListWidget.add(Text("${myData[i]}"));
}
}
void updateUI()
{
setState((){});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.red,
title: Text("Documents"),
actions: <Widget>[
],
),
body: Center(
child:
Column(children: myListWidget.isEmpty?[Text("Waiting...")]:myListWidget),
)
);
}
}
You can fix this by calling setState(() {}) method; setState
Notify the framework that the internal state of this object has changed.
Future getallDocument() async {
final QuerySnapshot result =
await Firestore.instance.collection("Users").getDocuments();
final List<DocumentSnapshot> documentos = result.documents;
documentos.forEach((data) {
myData.add(data.documentID);
print(myData);
});
for (int i = 0; i < (myData.length); i++) {
myListWidget.add(Text("${myData[i]}"));
}
setState(() {});
}

Why doesnt my void function update my int variable?

I have 3 int variable that I want to update via a void function during an initstate. I tried printing them out and the value is correct but when I try to display them in my container, it still shows 0.
int equipmentCount1 = 0;
int equipmentCount2 = 0;
int equipmentCount3 = 0;
#override
void initState() {
getEquipmentCount('Hammer', equipmentCount1);
getEquipmentCount('Spanner', equipmentCount2);
getEquipmentCount('Screwdriver', equipmentCount3);
super.initState();
}
void getEquipmentCount(String type, int counter) async {
await Firestore.instance
.collection('Notes')
.document('CarNotes')
.collection('PM Overview')
.document(type)
.collection(type)
.getDocuments()
.then((QuerySnapshot snapshot) {
setState(() {
return counter = snapshot.documents.length;
});
});
print(counter);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(equipmentCount1),
Text(equipmentCount2),
Text(equipmentCount3),
You should pass in a callback function so the state can be updated.
Here's an example:
#override
void initState() {
getEquipmentCount('Hammer', (int count) => setState(() {
equipmentCount1 = count;
}));
// same for the others
super.initState();
}
void getEquipmentCount(String type, ValueChanged<int> onCountChanged) {
Firestore.instance
.collection('Notes')
.document('CarNotes')
.collection('PM Overview')
.document(type)
.collection(type)
.getDocuments()
.then((QuerySnapshot snapshot) {
onCountChanged(snapshot.documents.length);
});
});
print(counter);
}
Also, await isn't necessary since you're using then on the Future, and the function inside setState doesn't need to return anything.
The value is still 0, because in your method you are only changing the counter variable inside the method and not the instance variables equipmentCount3. You can create a list to add all the 3 values, and then use that list inside the build method:
int equipmentCount1 = 0;
int equipmentCount2 = 0;
int equipmentCount3 = 0;
List<int> listOfEquipments = List();
void getEquipmentCount(String type, int counter) async {
await Firestore.instance
.collection('Notes')
.document('CarNotes')
.collection('PM Overview')
.document(type)
.collection(type)
.getDocuments()
.then((QuerySnapshot snapshot) {
setState(() {
listOfEquipments.add(snapshot.documents.length);
});
});
}
To add the list to the build method check the following:
https://stackoverflow.com/a/61548797/7015400

Updating only the tapped ListTile color in a list

How do I update the color of only the ListTile that is tapped?
Whatever I tried, it just changes the color of all tiles when tap.
How can I retrieve the data and change the color?
class _DesignState extends State<Design> {
var status=0;
var score =0;
Color getContainerColor() {
if (status == 0) {
return Colors.white;
} else if (status == 1) {
return Colors.green;
} else {
return Colors.red;
}
Widget _buildListItem(BuildContext context, DocumentSnapshot data) {
final record = Record.fromSnapshot(data);
final record1= Firestore.instance.collection('creds').document('123').get().then((DocumentSnapshot ds) {
var s =Record.fromSnapshot(ds);
score= s.score;
});
child: ListTile(
title: Text(record.name),
trailing: Text(record.score.toString()),
onTap: () { record.reference.updateData({'score': FieldValue.increment(score)}),
setState(){
status=1;
You're already on the right track, but it looks like you're managing the state of all of your list tile in the same StatefulWidget.
Instead you'll just need to split them up, so that every of your custom ListTile has it's on state. The loading of the data can happen in the parent component of your self build ListTile.
I'll provide you a short example application. The following example is without firebase, but it should be no problem to apply these changes to your application.
You'd simply have to do the data fetching inside the parent component and pass the score parameter of your example to MyListTile – just like the title in the example below.
This is runnablbe on it's own, you can simply copy it into a empty Flutter project:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
MyListTile(title: 'First'),
MyListTile(title: 'Second'),
MyListTile(title: 'Third'),
],
),
),
);
}
}
class MyListTile extends StatefulWidget {
final String title;
MyListTile({this.title});
#override
_MyListTileState createState() => _MyListTileState();
}
class _MyListTileState extends State<MyListTile> {
int status = 0;
get tileColor {
switch(status) {
case 0: {
return Colors.white;
}
case 1: {
return Colors.green;
}
default: {
return Colors.red;
}
}
}
#override
Widget build(BuildContext context) {
return Container(
color: tileColor,
child: ListTile(
title: Text(widget.title),
subtitle: Text('Status: $status'),
onTap: () => setState(() {
status++;
}),
),
);
}
}

waiting for async funtion to finish

I want to read some txts and store their text in an array. But because I need this array for my GUI it should wait until all is done.
Future<String> getFileData(String path) async {
return await rootBundle.loadString(path);
}
int topicNr = 3;
int finished = 0;
for (int topic = 1; topic <= topicNr; topic++) {
getFileData('assets/topic' + topic.toString() + '.txt').then(
(text) {
topics.add(text);
},
).whenComplete(() {
finished++;
});
}
while (finished < topicNr)
But when I run this code, finished won't update (I think because it is because the while loop runs on the main thread and so the async funtion can't run at the same time)
I could do this by just waiting, but this isn't really a good solution:
Future.delayed(const Duration(milliseconds: 10), () {
runApp(MaterialApp(
title: 'Navigation Basics',
home: MainMenu(),
));
});
How can I now just wait until all of those async Funtions have finished?
(sorry, I am new to Flutter)
One thing you could do is use a stateful widget and a loading modal. When the page is initialized, you set the view to be the loading modal and then call the function that gets the data and populate the data using set state. When you are done/when you are sure the final data has been loaded then you set the loading to false. See the example below:
class Page extends StatefulWidget {
page();
#override
State<StatefulWidget> createState() => new _Page();
}
class _Page extends State<Page>{
bool _loading = true; //used to show if the page is loading or not
#override
void initState() {
getFileData(path); //Call the method to get the data
super.initState();
}
Future<String> getFileData(String path) async {
return await rootBundle.loadString(path).then((onValue){
setState(() { //Call the data and then set loading to false when you are done
data = on value.data;
_loading = false;
});
})
}
//You could also use this widget if you want the loading modal ontop your page.
Widget IsloadingWidget() {
if (_loading) {
return Stack(
children: [
new Opacity(
opacity: 0.3,
child: const ModalBarrier(
dismissible: false,
color: Colors.grey,
),
),
new Center(
child: new CircularProgressIndicator(
valueColor:
new AlwaysStoppedAnimation<Color>(Colors.green),
strokeWidth: 4.0,
),
),
],
);
} else {
return Container();
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
//If loading, return a loading widget, else return the page.
_loading ?
Container(
child: Center(
child: CircularProgressIndicator(
valueColor: new AlwaysStoppedAnimation<Color>(
Colors.blue))))
:Column(
children:<Widget>[
//Rest of your page.
]
)
]))
}
}
You could also set the fields of the initial data to empty values and the use set state to give them their actual values when you get the data.
so for example
string myvalue = " ";
#override
void initState() {
getFileData(path); //Call the method to get the data
super.initState();
}
//then
Future<String> getFileData(String path) async {
return await rootBundle.loadString(path).then((onValue){
setState(() { //Call the data and then set loading to false when you are done
data = on value.data;
myValue = onValue.data['val'];
_loading = false;
});
})
}
Let me know if this helps.
Use the FutureBuilder to wait for the API call to complete before building the widget.
See this example: https://flutter.dev/docs/cookbook/networking/fetch-data
runApp(MaterialApp(
title: 'Navigation Basics',
home: FutureBuilder(
future: getFileData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return MainMenu()
} else {
return CircularProgressIndicator();
}
));

Resources