How to force rebuild list? - oop

I am make random chat app use FirestoreAnimatedList (FirebaseAnimatedList for Firestore).

In general is work.
But there is flicker when show user avatar (Icons.account_circle). For example sometime it show extra Icon (wrong) and then later not show (correct).
So I think maybe is because of Widget rebuild? But I no know how solve.
final Map<int, dynamic> map = {};
...
body: FirestoreAnimatedList(
query: firestore.collection('messages').snapshots(),
itemBuilder: (
BuildContext context,
DocumentSnapshot snapshot,
Animation<double> animation,
int index,
) {
map[index] = snapshot;
return FadeTransition(
opacity: animation,
child: MessageItem(
index: index,
document: snapshot,
map: map,
onTap: _removeMessage,
),
);
},
class MessageItem extends StatelessWidget {
…
#override
Widget build(BuildContext context) {
return Container(
child: new Row(
children: <Widget>[
new Container(
child:
_sameUser() ?
new Icon(
Icons.account_circle
)
:
Container()
bool _sameUser () {
assert(index >= 0);
assert(map != null);
return map[index + 1] != null && map[index + 1]['fromUser'] == map[index]['fromUser'];
}
Anyone help?
Thanks!

Related

API data display - flutter card widget

Get API connection is successful. but I have no idea how to map data.. how is connect snapshot.data,
I want to display data in a Stack widget.
https://flutter.dev/docs/cookbook/networking/fetch-data
I learned this from the association with this source
Future<News> fetchNews() async {
final response =
await http.get('#url');
print(response.body);
if (response.statusCode == 200) {
return News.fromJson(jsonDecode(response.body));
} else {
throw Exception('Failed to load news');
}
}
class News {
final String title;
final String description;
final String image;
News({this.title, this.description, this.image});
factory News.fromJson(Map<String, dynamic> json) {
return News(
title: json['title'],
description: json['description'],
image: json['image'],
);
}
}
Widget _buildPage(BuildContext context, int direction) {
Size size = MediaQuery.of(context).size;
return SafeArea(
child: FutureBuilder<News>(
future: futureNews,
builder: (context, snapshot) {
if (snapshot.hasData) {
return newsCard(snapshot.data);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return CircularProgressIndicator();
},
),
);
}
Widget newsCard(size){
return Stack(
children: <Widget>[
Container(
child: Image.network(
image,
fit: BoxFit.cover
),
height: size.height/2.0,
),
),
],
);
}
Your code is a bit messy and it doesn't make much sense. It seems obvious that the data is not getting displayed because you're passing your News object to your newsCard function, but the parameter seems to be expecting Size. I also don't know where are you getting the image property from since it doesn't seem to be declared anywhere in the code.
Doing some changes to your code, a minimal working function that returns your widget would look like this:
Widget _buildPage(BuildContext context, int direction) {
Size size = MediaQuery.of(context).size;
return SafeArea(
child: FutureBuilder<News>(
future: futureNews,
builder: (context, snapshot) {
if (snapshot.hasData) {
return newsCard(snapshot.data, size);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
}
return CircularProgressIndicator();
},
),
);
}
Widget newsCard(News news, Size size){
return Stack(
children: <Widget>[
Container(
child: Image.network(
news.image,
fit: BoxFit.cover
),
height: size.height/2.0,
),
),
],
);
}
UPDATE
If you're getting the error you mentioned bellow, is likely that your response from the get request is a list of News and not just one News object, therefore you'll have to change this:
return News.fromJson(jsonDecode(response.body));
To this:
return jsonDecode(response.body).map<News>((object) => News.fromJson(object)).toList();
The return type of fetchNews() should be Future<List<News>> instead of Future<News>

Flutter app error "NoSuchMethodError: The getter 'length' was called on null"

Am getting this error and can't find the why, the app shows a google map calling hospital from google map places api, api call returns fine when used in chrome, but the app fail to build showing the "NoSuchMethodError: The getter 'length' was called on null" error. Thanks for your help.
class Search extends StatelessWidget {
#override
Widget build(BuildContext context) {
final currentPosition = Provider.of<Position>(context);
final placesProvider = Provider.of<Future<List<Place>>>(context);
return FutureProvider(
create: (context) => placesProvider,
child: Scaffold(
body: (currentPosition != null)
? Consumer<List<Place>>(
builder: (_, places, __) {
return Column(
children: <Widget>[
Container(
height: MediaQuery.of(context).size.height / 3,
width: MediaQuery.of(context).size.width,
child: GoogleMap(
initialCameraPosition: CameraPosition(
target: LatLng(currentPosition.latitude,
currentPosition.longitude),
zoom: 16.0),
zoomGesturesEnabled: true,
mapToolbarEnabled: true,
myLocationEnabled: true,
scrollGesturesEnabled: true,
),
),
SizedBox(
height: 10.0,
),
Expanded(
child: ListView.builder(
itemCount: places.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text(places[index]?.name),
),
);
}),
)
],
);
},
)
: Center(
child: CircularProgressIndicator(),
),
),
);
}
}
The api request works fine when called in chrome, problem is when building the app. This is my places_service.dart file:
class PlacesService {
final key = 'xxxxxxxxxxxxxxxxxxxxx-ttttttttttttttttt';
Future<List<Place>> getPlaces(double lat, double lng) async {
var response = await http.get('https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=$lat,$lng&type=hospital&rankby=distance&key=$key');
var json = convert.jsonDecode(response.body);
var jsonResults = json['results'] as List;
return jsonResults.map((place) => Place.fromJson(place)).toList();
}
}
Problem is this line:
itemCount: places.length,
If you use constructions like places.length the programming code can fail on two places:
when places = null
when places.length = null (your situation)
You cannot call methods and properties on null-objects; that is why you get this error. So before you call places.length you must be sure that both situations cannot occur. How this is done and if the check is really necessary depends on the rest of your program. One way to check is:
itemCount: places.length != null ? places.length : 0;

Trying to make radio buttons with using Streambuilder and Bloc in Flutter. but don't work

I tried to make radiobuttons by using Streambuilder and Bloc.
so I made streamcontroler and when radiobuttons clicked,
I made streamcontrl.add(value) implemented, but Streambuilder don't listen
that stream. I tested onchanged value of radio. and
Please figure out what's wrong with it.
This is full code.
import 'package:flutter/material.dart';
import 'dart:async';
void main(){
runApp(new MaterialApp(
home: new MyApp(),
));
}
class bloc {
StreamController <int> ctrl = StreamController() ;
get blocvalue => ctrl.stream;
void setvalue (value ) {
ctrl.sink.add(value) ; }
}
class MyApp extends StatefulWidget {
#override
_State createState() => new _State();
}
class _State extends State<MyApp>{
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Name here'),
),
body: new Container(
padding: new EdgeInsets.all(15.0),
child: new Center(
child: new Column(
children: <Widget>[
StreamBuilder(
stream: bloc().blocvalue,
initialData: 0,
builder: (BuildContext context, AsyncSnapshot <int> snapshot)
{
List<Widget> list = new List<Widget>();
for(int i = 0; i < 3; i++){
list.add(new RadioListTile(
value: i,
groupValue: snapshot.data,
onChanged: bloc().setvalue,
activeColor: Colors.green,
controlAffinity: ListTileControlAffinity.trailing,
title: new Text('Item: ${i}'),
dense: true,
// subtitle: new Text('sub title'),
));
}
return Column(children: list,); })
],
),
),
),
);
}
}
As mentioned by pksink in the comments, you're creating a new bloc inside build by calling setting it in StreamBuilder as bloc().blocValue. What you can do here is declare it as final myBloc = bloc(); outside of the bloc and set it on your StreamBuilder as myBloc.blocValue. With this, a new instance of bloc won't be created with every rebuild of Widget build.

Flutter Shared Preferences saving values, not displaying

I have a list of courses. The user marks each course complete using a checkbox on the ListTile.
I implemented Shared Preferences so the list of completed courses persists when the user closes the app.
The values are saving, but when the app is closed (in the emulator or through the IDE) and reopened, the UI shows the value as false (Even when the Terminal says the value is True).
When I hot restart, the UI shows the value as True (Which was expected from the start). I haven't been able to get the UI to show correctly using the emulator buttons or on a device.
How can I get the UI to show the values correctly right away?
SharedPreferences prefs;
void getResult(Course course) async {
prefs = await SharedPreferences.getInstance();
results[course.courseResult] = prefs.getBool(course.courseResult) ?? false;
print('${course.courseTitle} Result: ${results[course.courseResult]}');
setState(() {
results[course.courseResult];
});
}
Future<bool> setResult(Course course) async {
prefs = await SharedPreferences.getInstance();
print ('${course.courseTitle} SET TO ${results[course.courseResult]}');
return prefs.setBool(course.courseResult, results[course.courseResult]);
}
initState() {
super.initState();
getResult(widget.entry);
}
Future onChanged(bool value, Course course) {
setState(() {
results[course.courseResult] = value;
});
return setResult(course);
}
Here is the full code (Though I did shorten the lists for space purposes, and left out the pages that aren't affected by this error...)
import 'package:flutter/material.dart';
import 'main.dart';
import 'CourseList.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:async';
import 'package:url_launcher/url_launcher.dart';
import 'package:intl/intl.dart';
class LearningPlan extends StatefulWidget{
LearningPlanState createState() => new LearningPlanState();
}
class LearningPlanState extends State<LearningPlan> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppBar(
title: Text('Learning Plan'),
),
drawer: MyDrawer(),
body: ListView.builder(
itemBuilder: (BuildContext context, int index) =>
new CourseTile(courseList[index]),
itemCount: courseList.length,
),
);
}
}
class CourseTile extends StatefulWidget {
CourseTile(this.entry);
final Course entry;
CourseTileState createState() => new CourseTileState();
}
class CourseTileState extends State<CourseTile> {
//Detail Card
Future<Null> _launched; // ignore: unused_field
Future<Null> _launchInWebViewOrVC(String url) async {
if (await canLaunch(url)) {
await launch(url, forceSafariVC: false, forceWebView: false);
} else {
throw 'Could not launch $url';
}
}
Widget selfDirectedURL(Course course) {
if (course.courseMethod == 'Self-Directed') {
return new IconButton(
icon: Icon(Icons.cloud_download),
onPressed: () => setState(() {
_launched = _launchInWebViewOrVC(course.courseURL);
}),
);
} else {
return new Container();
}
}
Future<Null> courseDetails(Course course) async {
await showDialog(
context: context,
child: new SimpleDialog(
title: Text(course.courseTitle),
children: <Widget>[
Stack(
children: <Widget>[
Center(child: Image.asset(course.courseImage,
colorBlendMode: BlendMode.lighten,
color: fkBlue25,
height: 200.0,
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(course.courseDescription),
),
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
selfDirectedURL(course),
FlatButton(
onPressed: (){
Navigator.pop(context);
},
child: Text('OK'),
),
],
),
],
));
}
//CheckBox Constructors
SharedPreferences prefs;
void getResult(Course course) async {
prefs = await SharedPreferences.getInstance();
results[course.courseResult] = prefs.getBool(course.courseResult) ?? false;
print('${course.courseTitle} Result: ${results[course.courseResult]}');
setState(() {
results[course.courseResult];
});
}
Future<bool> setResult(Course course) async {
prefs = await SharedPreferences.getInstance();
print ('${course.courseTitle} SET TO ${results[course.courseResult]}');
return prefs.setBool(course.courseResult, results[course.courseResult]);
}
initState() {
super.initState();
getResult(widget.entry);
}
Future onChanged(bool value, Course course) async {
final result = await setResult(course);
setState(() {
results[course.courseResult] = value;
});
return result;
}
//Main Tile
Widget buildTiles(Course course) {
return Card(
shape: Border.all(
color: fkBlue,
),
margin: EdgeInsets.all(16.0),
elevation: 8.0,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ListTile(
title: Text(course.courseTitle),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(course.courseCode),
Text(course.courseMethod)
],
),
leading: SizedBox(
height: 60.0,
width: 60.0,
child: Image.asset(course.courseImage)),
trailing: Column(
children: <Widget>[
Text(results[course.courseResult] ? 'Complete' : 'Incomplete',
),
Checkbox(
value: results[course.courseResult],
onChanged: (bool value) {
onChanged(value, course);
if (value == true) {
snackBarCompleted(course);
} else {
snackBarUnCompleted(course);
}
},
),
]
),
onTap: () {
courseDetails(course);
}
),),
);
}
#override
Widget build(BuildContext context) {
return buildTiles(widget.entry);
}
void snackBarCompleted(course) {
Scaffold.of(context).showSnackBar(
SnackBar(content: Text(
'${course.courseTitle} completed on ${DateFormat.yMd().format(DateTime.now()).toString()}'
),
backgroundColor: fkBlue,
duration: Duration(seconds: 3),
),
);
}
void snackBarUnCompleted(course) {
Scaffold.of(context).showSnackBar(
SnackBar(content: Text('${course.courseTitle} no longer marked \"Complete\"'
),
duration: Duration(seconds: 3),
),
);
}
}
//Learning Schedule Page
class LearningSchedule extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppBar(
title: Text('Schedule'),
),
drawer: MyDrawer(),
body: ListView.builder(
itemBuilder: (BuildContext context, int index) =>
new LearningScheduleBuilder(courseList[index]),
itemCount: courseList.length,
),
);
}
}
class LearningScheduleBuilder extends StatelessWidget {
LearningScheduleBuilder(this.entry);
final Course entry;
Widget buildList (Course course) {
return Text(course.courseTitle,
style: new TextStyle(color: results[course.courseResult] ? Colors.grey : fkBlue),);
}
#override
Widget build(BuildContext context) {
return buildList(entry);
}
}
final List<Course> courseList = <Course>[
new Course(
courseTitle: 'Company Orientation',
coursePreReq: 'N/A',
courseCode: 'HR',
courseURL: '',
courseMethod: 'Facilitator-Led',
courseImage: 'assets/courseImage/logo.png',
courseDescription:
'Company overview; Benefits package and documents; Ethics and Compliance Training, Introduction to learning programs; Computer orientation; Lab tour; Safety training.',
courseAudience: 'BCAE BCCC ITAE ITCC TCTAE TCTCC PlasmaCC PlasmaAE',
courseResult: 'result1',
),
new Course(
courseTitle: 'Intro to Learning Program',
coursePreReq: 'N/A',
courseCode: 'Nicole Asma',
courseURL: '',
courseMethod: 'Facilitator-Led',
courseImage: 'assets/courseImage/logo.png',
courseDescription:
'Overview of onboarding program; Components of North America University; Support available for all learning units; introduction to Learning and Development Team Overview of WebEx calls.',
courseAudience: 'BCAE BCCC ITAE ITCC TCTAE TCTCC PlasmaCC PlasmaAE',
courseResult: 'result2',
),
class Course {
final String courseTitle;
final String coursePreReq;
final String courseCode;
final String courseDescription;
final String courseImage;
final String courseMethod;
final String courseURL;
final String courseAudience;
final String courseResult;
const Course({
this.courseTitle,
this.coursePreReq,
this.courseCode,
this.courseDescription,
this.courseImage,
this.courseMethod,
this.courseURL,
this.courseAudience,
this.courseResult,
});
Course.fromMap(Map<String, dynamic> map)
: courseTitle = map['courseTitle'],
coursePreReq = map['coursePreReq'],
courseCode = map['courseCode'],
courseDescription = map['courseDescription'],
courseImage = map['roocourseImagem'],
courseMethod = map['courseMethod'],
courseURL = map['courseURL'],
courseAudience = map['courseAudience'],
courseResult = map['courseResult'];
}
Map results = {
'result1': false,
'result2': false,
'result3': false,
'result4': false,
Could you make this little change? :
Change this :
Future onChanged(bool value, Course course) {
setState(() {
results[course.courseResult] = value;
});
return setResult(course);
}
To this:
Future onChanged(bool value, Course course) async {
final result = await setResult(course);
setState(() {
results[course.courseResult] = value;
});
return result;
}
UPDATE
Replace your initState method with this:
_onLayoutDone(_){
getResult(widget.entry);
}
#override
void initState() {
WidgetsBinding.instance.addPostFrameCallback(_onLayoutDone);
super.initState();
}

Strange red Block under Flutter TextField Widget

I'm working on a shopping list in one tab of my flutter app but under the Input field i always get and strange red block when the keyboard comes up (red block stays there until keyboard goes away)
RedBlock which appears with keyboard
Debug report which shows up after clicking into the field
Performing full restart...
Restarted app in 1.172ms.
D/ViewRootImpl#da3a8cd[MainActivity](28869): ViewPostImeInputStageprocessPointer 0
D/ViewRootImpl#da3a8cd[MainActivity](28869): ViewPostImeInputStageprocessPointer 1
I/flutter (28869): [{name: Lukas, id: 1, value: 32}, {name: Sophie, id: 2, value: 20}, {name: Peter, id: 3, value: 45}]
D/ViewRootImpl#da3a8cd[MainActivity](28869): ViewPostImeInputStage processPointer 0
D/ViewRootImpl#da3a8cd[MainActivity](28869): ViewPostImeInputStage processPointer 1
V/InputMethodManager(28869): Starting input: tba=android.view.inputmethod.EditorInfo#b63ece2 nm : com.yourcompany.flutterapp ic=io.flutter.plugin.editing.InputConnectionAdaptor#484e873
I/InputMethodManager(28869): [IMM] startInputInner - mService.startInputOrWindowGainedFocus
D/InputTransport(28869): Input channel constructed: fd=101
D/InputTransport(28869): Input channel destroyed: fd=100
D/InputMethodManager(28869): ISS - flag : 0Pid : 28869 view : com.yourcompany.flutterapp
D/ViewRootImpl#da3a8cd[MainActivity](28869): MSG_RESIZED: frame=Rect(0, 0 - 1080, 2220) ci=Rect(0, 63 - 0, 918) vi=Rect(0, 63 - 0, 918) or=1
D/ViewRootImpl#da3a8cd[MainActivity](28869): Relayout returned: oldFrame=[0,0][1080,2220] newFrame=[0,0][1080,2220] result=0x1 surface={isValid=true -887126016} surfaceGenerationChanged=false
Here you can see my code i have written:
import 'dart:async';
import 'dart:core';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sqflite/sqflite.dart';
class ShoppingBasket extends StatefulWidget {
#override
ShoppingBasketState createState() => new ShoppingBasketState();
}
class ShoppingBasketState extends State<ShoppingBasket> {
Directory documentsDirectory;
String dirPath;
Database database;
List<Map> listRecords;
Widget listView;
final TextEditingController _controller1 = new TextEditingController(); // name field
final TextEditingController _controller2 = new TextEditingController(); // value field
#override
void initState() {
listView = beforeDataFetchIsFinished();
getPathAndCheckForDbAndPrepareListView();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Column(
children: <Widget>[
inputFieldCard(),
listView, //--> List view gets after all data was fetched here
],
),
);
}
//-----------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------
//View Build ------------------------------------------------------------------------
/// Set the listview variable with an CircularPorgressIndicator.
/// gets overriden if the real listview has finished.
Widget beforeDataFetchIsFinished() {
return new Container(
margin: new EdgeInsets.fromLTRB(0.0, 30.0, 0.0, 0.0),
child: new Center(
child: new CircularProgressIndicator(
strokeWidth: 2.0,
),
),
);
}
/// The Inputfield card in one methode.
/// Returns the InputCard as one widget.
Widget inputFieldCard() {
return new Container(
child: new Card(
child: new Container(
child: new Column(
children: <Widget>[
new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Container(
width: 150.0,
padding: new EdgeInsets.fromLTRB(10.0, 20.0, 10.0, 10.0),
child: new TextField(
controller: _controller1,
decoration: new InputDecoration(
hintText: 'Name...',
),
),
),
new Container(
width: 150.0,
padding: new EdgeInsets.fromLTRB(10.0, 20.0, 10.0, 10.0),
child: new TextField(
keyboardType: TextInputType.number,
controller: _controller2,
decoration: new InputDecoration(
hintText: 'Value...',
),
),
),
],
),
new Container(
padding: new EdgeInsets.fromLTRB(0.0, 0.0, 0.0, 20.0),
child: new RaisedButton(
color: Colors.green,
child: new Text('Insert Data', style: new TextStyle(color: Colors.white),),
onPressed: () {
insertToDb(_controller1.text, _controller2.text);
_controller1.clear();
_controller2.clear();
},
),
),
],
)
),
), //top card end
);
}
/// the CircularProgressIndicator gets overiden if this
/// methode gets all its data --> then rerender.
Widget injectListViewAfterAllDataIsFetched() {
return new Card(
child: new Container(
child: new ListView.builder(
shrinkWrap: true, //<-- Necessary because Listveiw inside Column
itemCount: listRecords == null ? 0 : listRecords.length,
itemBuilder: (BuildContext context, int index) {
return new ListTile(
title: new Text(listRecords[index]['name']),
);
},
),
),
);
}
//-----------------------------------------------------------------------------------
//-----------------------------------------------------------------------------------
//Data-Base Operations --------------------------------------------------------------
/// Start up --> Open db and fetching data when complete
/// start render engine again.
Future<bool> getPathAndCheckForDbAndPrepareListView() async {
documentsDirectory = await getApplicationDocumentsDirectory();
String dirPath = documentsDirectory.path;
List content = documentsDirectory.listSync();
final File file = new File(dirPath + '/myDataBase.db');
if(!content.contains(file)) { //Check if db exists
await createDbIfNotExists(file); //if not create it
}
print(await getRecords());
listRecords = await getRecords();
print(listRecords);
setState(() {
listView = injectListViewAfterAllDataIsFetched();
});
return true;
}
/// Inserting data into the data base.
/// #return true.
Future<bool> insertToDb(String name, String value) async {
if(name != '' && value != '') {
var valueSql = int.parse(value);
String sql = 'INSERT INTO Test(name, value) VALUES("$name", $valueSql)';
await database.inTransaction(() async {
await database.rawInsert(sql);
});
listRecords = await getRecords();
setState(() {
listView = injectListViewAfterAllDataIsFetched();
});
return true;
} else {
return false;
}
}
/// Gives the whole Db back.
/// #return Map with all records.
Future<List<Map>> getRecords() async {
return await database.rawQuery('SELECT * FROM Test');
}
/// Creating the given File (should be an .db file).
/// #param file Gives the file (.db) which gets created.
/// #return true.
Future<bool> createDbIfNotExists(File file) async {
database = await openDatabase(file.path, version: 1,
onCreate: (Database db, int version) async {
await db.execute(
"CREATE TABLE Test (id INTEGER PRIMARY KEY, name TEXT, value INTEGER)");
});
return true;
}
}
Does someone of you understand why this is showing up? And have an smart Solution to fix it?
Edit: Some Photos which show my Keyboard with shortcuts and without them
I was facing the same problem. Just set resizeToAvoidBottomPadding false in you scaffold and it should solve the problem.
The red bar indicates that the content of one of your containers or columns are bigger, than allowed by their parent. I cant reproduce this issue on iOS or Android. Also, why is the raised button not displayed in your screenshot?