I have a StatefulWidget which state renders different Widget depending on loading state (Loading -> Loaded/Error):
// widget
class ListNotesScreen extends StatefulWidget {
static const route = '/listNotes';
static navigateTo(BuildContext context, [bool cleanStack = true]) =>
Navigator.pushNamedAndRemoveUntil(context, route, (_) => !cleanStack);
final String title;
final ListNotesUseCase _useCase;
final VoidCallback _addNoteCallback;
ListNotesScreen(this._useCase, this._addNoteCallback, {Key key, this.title}) : super(key: key);
#override
_ListNotesScreenState createState() => _ListNotesScreenState();
}
// state
class _ListNotesScreenState extends State<ListNotesScreen> {
ListNotesLoadState _state;
Future<ListNotesResponse> _fetchNotes() async {
return widget._useCase.listNotes();
}
#override
initState() {
super.initState();
_loadNotes();
}
_loadNotes() {
setState(() {
_state = ListNotesLoadingState();
});
_fetchNotes().then((response) {
setState(() {
_state = ListNotesLoadedState(response.notes);
});
}).catchError((error) {
setState(() {
_state = ListNotesLoadErrorState(error);
});
});
}
#override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: Text('Notes list'),
actions: <Widget>[
IconButton(icon: Icon(Icons.add), onPressed: widget._addNoteCallback),
IconButton(icon: Icon(Icons.refresh), onPressed: () => _loadNotes())
],
),
body: _state.getWidget());
}
// loading states
// State:
#sealed
abstract class ListNotesLoadState {
Widget getWidget();
}
// Loading
class ListNotesLoadingState extends ListNotesLoadState {
#override
Widget getWidget() => Center(child: CircularProgressIndicator(value: null));
}
// Loaded
class ListNotesLoadedState extends ListNotesLoadState {
final List<Note> _notes;
ListNotesLoadedState(this._notes);
#override
Widget getWidget() => ListView.builder(
itemBuilder: (_, int index) => NoteItemWidget(this._notes[index]),
itemCount: this._notes.length,
padding: EdgeInsets.all(18.0));
}
Here is the test for the widget:
void main() {
testWidgets('Notes list is shown', (WidgetTester tester) async {
final title1 = 'Title1';
final title2 = 'Title2';
final body1 = 'Body1';
final body2 = 'Body2';
var notes = [
Note('1', title1, body1),
Note('2', title2, body2),
];
final listUseCase = TestListNotesInteractor(notes);
final widget = ListNotesScreen(listUseCase, null, title: 'List notes');
await tester.pumpWidget(widget);
await tester.pumpAndSettle();
expect(find.text('someInvalidString'), findsNothing);
expect(find.text(title1), findsOneWidget);
expect(find.text(title2), findsOneWidget);
expect(find.text(body1), findsOneWidget);
expect(find.text(body2), findsOneWidget);
// TODO: fix the test (tested manually and it works)
});
}
So widget tester is expected to wait until the state it set to loading in initState(), then _loadNotes moves it to ListNotesLoadedState and ListNotesLoadedState.getWidget() to return ListView with expected string (NoteItemWidget root and few Text with expected string).
However the test fails. What's the reason (i was able to use test interactors in the app and visually see expected texts)? How can i analyze the actual Widgets tree on test failure?
I tend to think that WidgetTester did not wait for Future to be completed (though it's expected to be mocked and be sync behind the scenes, please correct me).
One can find the project on Github (make sure to call flutter packages pub run build_runner build to generate json de-/serialize code).
I've found the reason: MaterialApp (or probably any app) should be the root of widgets tree!
final widget = MaterialApp(home: ListNotesScreen(interactor, null)); // succeeds
instead of:
final widget = ListNotesScreen(interactor, null); // fails
Also i've removed unused title property so the test code is a bit different form what i used originally:
final widget = ListNotesScreen(listUseCase, null, title: 'List notes');
It's not mentioned in the docs (is it the reason actually?) though the test code has it. Please let me know if i miss something.
Related
I am trying to achieve a course in AWS to connect Amplify in Flutter. But inside 'Create Authentication Flow', when it adds MaterialPage inside a pages, inside Navigator it doesn't found.
I have this error in the MaterialPage:
The method 'MaterialPage' isn't defined for the type '_MyAuthStateState'. Try correcting the name to the name of an existing method, or defining a method named 'MaterialPage'.
My current code (main.dart):
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _authService = AuthService();
#override
void initState() {
super.initState();
_authService.showLogin();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Photo Gallery App',
theme: ThemeData(visualDensity: VisualDensity.adaptivePlatformDensity),
home: StreamBuilder<AuthState>(
// 2
stream: _authService.authStateController.stream,
builder: (context, snapshot) {
// 3
if (snapshot.hasData) {
return Navigator(
pages: [
// 4
// Show Login Page
if (snapshot.data.authFlowStatus == AuthFlowStatus.login)
MaterialPage(
child: LoginPage(
shouldShowSignUp: _authService.showSignUp,
didProvideCredentials:
_authService.loginWithCredentials,
),
),
// 5
// Show Sign Up Page
if (snapshot.data.authFlowStatus == AuthFlowStatus.signUp)
MaterialPage(
child: SignUpPage(
shouldShowLogin: _authService.showLogin,
didProvideCredentials: _authService.signUpWithCredentials,
)),
if (snapshot.data.authFlowStatus ==
AuthFlowStatus.verification)
MaterialPage(
child: VerificationPage(
didProvideVerificationCode:
_authService.verifyCode))
],
onPopPage: (route, result) => route.didPop(result),
);
} else {
// 6
return Container(
alignment: Alignment.center,
child: CircularProgressIndicator(),
);
}
}),
);
}
}
I'm guessing your _authService.showLogin() inside initState is trying to perform some navigation, i.e. showing a Widget. Since MyApp doesn't connect itself to the top level Navigator automatically, you can't access/use Navigator functionality.
A MaterialApp will hook you up the top-level Navigator for you automatically.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
// ↓ Make MaterialApp your root widget
return MaterialApp(
title: 'Photo Gallery App',
theme: ThemeData(visualDensity: VisualDensity.adaptivePlatformDensity),
home: MyAuthStatePage(),
)
}
}
class MyAuthStatePage extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyAuthState();
}
class MyAuthState extends State<MyAuthStatePage> {
final _authService = AuthService();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
// TODO: implement build
_authService.showLogin();
}
}
Also, initState prob. isn't the right place to be navigating/showing widgets, rather it's there for preparing state that'll be used by widgets inside the build method.
I'm trying to make a buttomtabbar, where there is a List (Stack) on the first Tap page. Somehow there is this error and I just can't figure out why...
This is my Home file:
class Home extends StatelessWidget {
final AuthService _auth = AuthService();
#override
Widget build(BuildContext context) {
return TabContainerIndexedStack();
}}
This is TabContainerIndexStack():
class TabContainerIndexedStack extends StatefulWidget {
TabContainerIndexedStack({Key key}) : super(key: key);
#override
_TabContainerIndexedStackState createState() =>
_TabContainerIndexedStackState();
}
class _TabContainerIndexedStackState extends State<TabContainerIndexedStack> {
int tabIndex = 0;
List<Widget> listScreens;
#override
void initState() {
super.initState();
listScreens = [
Tab1(),
Tab2(),
Tab3(),
];
}
// #override
// bool get wantKeepAlive =>
// true; //by default it will be null, change it to true.
#override
Widget build(BuildContext context) {
return MaterialApp(
color: Colors.yellow,
home: Scaffold(
body: IndexedStack(index: tabIndex, children: listScreens),
bottomNavigationBar: BottomNavigationBar(
currentIndex: tabIndex,
onTap: (int index) {
setState(() {
tabIndex = index;
});
},
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Tab1'),
),
BottomNavigationBarItem(
icon: Icon(Icons.report_problem),
title: Text('Tab2'),
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
title: Text('Tab3'),
),
]),
backgroundColor: Theme.of(context).primaryColor,
),
);
}
}
This is my first Tab (other ones work!!!)
class Tab1 extends StatefulWidget {
#override
_Tab1State createState() => _Tab1State();
}
class _Tab1State extends State<Tab1> with AutomaticKeepAliveClientMixin<Tab1> {
#override
void initState() {
super.initState();
print('initState Tab1');
}
#override
Widget build(BuildContext context) {
print('build Tab1');
return Scaffold(
appBar: AppBar(
title: Text('Tab1'),
),
body: AuftraegeList()
);
}
#override
bool get wantKeepAlive => true;
}
I think the problem is right there in the "body: AuftraegeList()..." above..
Here is the file AuftraegeList():
class AuftraegeList extends StatefulWidget {
#override
_AuftraegeListState createState() => _AuftraegeListState();
}
class _AuftraegeListState extends State<AuftraegeList> {
#override
Widget build(BuildContext context) {
final auftraege = Provider.of<List<Auftrag>>(context);
return ListView.builder(
itemCount: auftraege.length,
itemBuilder: (context, index){
return AuftragTile(auftrag: auftraege[index]);
},
);
}
}
I hope this is enough to solve my problem. I'm very new to Flutter, so it would be nice, if you can say where EXACTLY I have to change WHAT. Thank you so much!!!
EDIT: Here is the Code of my home.dart, which is the code, which represents the list in my main view.
class Home extends StatelessWidget {
final AuthService _auth = AuthService();
#override
Widget build(BuildContext context) {
return TabContainerIndexedStack();
/*
return StreamProvider<List<Auftrag>>.value(
value: DatabaseService().auftraege,
child: Scaffold(
bottomNavigationBar: btmBar(),
backgroundColor: Colors.blue[50],
appBar: AppBar(
title: Text('Home title'),
backgroundColor: Colors.blue,
elevation: 0.0,
actions: <Widget>[
FlatButton.icon(
icon: Icon(Icons.person),
label: Text('logout'),
onPressed: () async{
await _auth.signOut();
},
)
],
),
body: AuftraegeList(),
),
);
*/
}
}
(Its the part I commented out)
Thanks!!
EDIT (2) !!!
Latest edit:
So my first tap class now looks like this (I changed in the Widget build body [] to databaseService.auftraege after declaring databaseService at first).
class Tab1 extends StatefulWidget {
#override
_Tab1State createState() => _Tab1State();
}
class _Tab1State extends State<Tab1> with AutomaticKeepAliveClientMixin<Tab1> {
// Where should I put this line? Whereever I put this, it gives me errors (already imported services/database.dart)
final DatabaseService databaseService = Provider.of<DatabaseService()>(context);
#override
void initState() {
super.initState();
print('initState Tab1');
}
#override
Widget build(BuildContext context) {
print('build Tab1');
return Scaffold(
appBar: AppBar(
title: Text('Tab1'),
),
body: Provider(
create: (context) => databaseService.auftraege,
child: AuftraegeList(),
)
);
}
#override
bool get wantKeepAlive => true;
}
Maybe its also helpful to show you my services/database
class DatabaseService{
final String uid;
DatabaseService({ this.uid });
// collection reference
final CollectionReference auftraegeCollection = Firestore.instance.collection('auftraege');
Future updateUserData(String title, String info, int price, String user) async{
return await auftraegeCollection.document(uid).setData({
'title' : title,
'info': info,
'price': price,
'user': user,
});
}
List<Auftrag> _auftragListFromSnapshot(QuerySnapshot snapshot){
return snapshot.documents.map((doc){
return Auftrag(
title: doc.data['title']?? '',
info: doc.data['info']?? '',
price: doc.data['price']?? 0,
user: doc.data['user']?? '',
);
}).toList();
}
// get auftraege stream
Stream <List<Auftrag>> get auftraege {
return auftraegeCollection.snapshots()
.map(_auftragListFromSnapshot);
}
}
When leave the code like that, it gives me an error at this line:
final DatabaseService databaseService = Provider.of<DatabaseService()>(context);
in my tab1.dart class. It says "Only static members can be accessed in initializers" under "context" and "error: A comparison expression can't be an operand of another comparison expression" as well as "error: The operator '<' isn't defined for the class 'T Function(BuildContext, {listen: bool})'."
Maybe you know what to do. I think I just put this line at the wrong place.
### EDIT (3) ###
(Exception)
════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The following assertion was thrown building _BodyBuilder:
dependOnInheritedWidgetOfExactType>() or dependOnInheritedElement() was called before _Tab1State.initState() completed.
When an inherited widget changes, for example if the value of Theme.of() changes, its dependent widgets are rebuilt. If the dependent widget's reference to the inherited widget is in a constructor or an initState() method, then the rebuilt dependent widget will not reflect the changes in the inherited widget.
Typically references to inherited widgets should occur in widget build() methods. Alternatively, initialization based on inherited widgets can be placed in the didChangeDependencies method, which is called after initState and whenever the dependencies change thereafter.
The relevant error-causing widget was:
Scaffold file:///Users/xxxxxxxxxxxxxxx/AndroidStudioProjects/promi_prototype/lib/screens/home/tab_containter_indexedstack.dart:36:13
When the exception was thrown, this was the stack:
0 StatefulElement.dependOnInheritedElement. (package:flutter/src/widgets/framework.dart:4467:9)
1 StatefulElement.dependOnInheritedElement (package:flutter/src/widgets/framework.dart:4510:6)
2 StatefulElement.inheritFromElement (package:flutter/src/widgets/framework.dart:4458:12)
3 Element.inheritFromWidgetOfExactType (package:flutter/src/widgets/framework.dart:3556:14)
4 Provider.of (package:provider/src/provider.dart:259:19)
It says, the relevant error-causing widget was: tab_containter_indexedstack.dart, I already posted this code at the very beginning of my post. The simulator now only shows the blue background with the tabbar at the bottom. No text at the other tabs (worked before) and no error warning at tab1. Even no headings.
Greetings!! :)
.
.
.
### EDIT (4) ###
Oh my god xD Sorry for not working.. and THANK YOU for still helping!
Lets start my the error message:
The following assertion was thrown building Provider>>(dirty, state:
flutter: _DelegateWidgetState#eb784):
flutter: Tried to use Provider with a subtype of Listenable/Stream (Stream>).
flutter:
flutter: This is likely a mistake, as Provider will not automatically update dependents
flutter: when Stream> is updated. Instead, consider changing Provider for more specific
flutter: implementation that handles the update mechanism, such as:
flutter:
flutter: - ListenableProvider
flutter: - ChangeNotifierProvider
flutter: - ValueListenableProvider
flutter: - StreamProvider
The relevant error-causing widget was:
flutter: Provider>>
flutter: file:///Users/xxxxxxxxxxxx/AndroidStudioProjects/promi_prototype/lib/screens/my_tabs/tab1.dart:33:13
So there still is an issue with the Provider in tab1.dart. My guess was to change the Provider thing in AuftragList() because there I was using it the "old" way like Provider.of>(context), just like you mentioned in the edit 18 hours ago.
This is what I did (Out-commented was before):
class AuftraegeList extends StatefulWidget {
#override
_AuftraegeListState createState() => _AuftraegeListState();
}
class _AuftraegeListState extends State<AuftraegeList> {
DatabaseService databaseService ;
#override
Widget build(BuildContext context) {
databaseService = Provider.of<DatabaseService>(context);
// final auftraege = Provider.of<List<Auftrag>>(context);
return ListView.builder(
// itemCount: auftraege.length,
// itemBuilder: (context, index){
itemCount: databaseService.length, //RED MARK HERE
itemBuilder: (context, index){
return AuftragTile(auftrag: databaseService[index]); //RED MARK HERE
},
);
}
}
I thought this is ok now, but I get red marks under ".lenght" and under "[index]". Compiler sais, that I should create a getter in helpers/database.dart which was what I tried then. But no success. I deleted the getters there then.
Do you have an idea? Is it right to change the Provider thing in the AuftraegeList() even though the Compiler said issue is in tap1.dart?
.
EDIT Since you are not imediately using the value for the Stream provider in the Home class, you can use a Provider instead as follows;
class Home extends StatelessWidget {
final AuthService _auth = AuthService();
#override
Widget build(BuildContext context) {
return Provider(
create: (context) => DatabaseServices(),
child: TabContainerIndexedStack()
);
},
}
Then when you need the list, you have to call a provider of the Service class and not the list
final DatabaseService databaseService = Provider.of<DatabaseService>(context);
Then you can access an instance of the list like
databaseService.auftraege
Edit 1
Assuming you have the provider in the home class, don't need to wrap the provider around AuftraegeList() again. This is what you should do.
class _Tab1State extends State<Tab1> with AutomaticKeepAliveClientMixin<Tab1> {
final DatabaseService databaseService;
#override
void initState() {
databaseService = Provider.of<DatabaseService>(context);
super.initState();
print('initState Tab1');
}
#override
Widget build(BuildContext context) {
print('build Tab1');
return Scaffold(
appBar: AppBar(
title: Text('Tab1'),
),
body:
// The use the list directly in this class where needed like `databaseService.auftraege`
child: AuftraegeList(),
)
);
}
#override
bool get wantKeepAlive => true;
}
Note
In a stateful widget, the BuildContext is available in the initState method and the build method. So you can use BuildContext out of those methods unless you implicitly pass it.
If you need the list in AuftragList() class then get an instance
of the list using Provider.of<DatabaseService>(context)
If you need the list to automatically update when a need item is available you can use a StreamSubscription to listen to need stream and add to the list.
When calling the provider of pass the dataType and not the class. That is Provider.of<DatabaseService>(context) and not Provider.of<DatabaseService()>(context).
Edit 2
Since you are getting a stream for you List, use a steamBuilder to builder your Ui
class AuftraegeList extends StatefulWidget {
#override
_AuftraegeListState createState() => _AuftraegeListState();
}
class _AuftraegeListState extends State<AuftraegeList> {
DatabaseService databaseService ;
#override
Widget build(BuildContext context) {
databaseService = Provider.of<DatabaseService>(context);
// final auftraege = Provider.of<List<Auftrag>>(context);
return StreamBuilder<List<Auftrag>>(
stream: databaseService.auftraege,
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index){
return AuftragTile(auftrag: snapshot.data[index]);
}
);
}else if(snapshot.hasError){
return Center(child: Text("An error Errored");
}
return Center(child: CircularProgessIndicator();
},
);
}
}
Hope this works.
I have a ListView inside a StatelessWidget. It has items and every item contains a checkbox. When someone checks an item, I want the ListView to send this as a parameter to another page. But when I do that, it's giving me this error:
I/flutter ( 7067): The following UnsupportedError was thrown while handling a gesture:
I/flutter ( 7067): Unsupported operation: Cannot add to an unmodifiable list
I/flutter ( 7067): When the exception was thrown, this was the stack:
and this is my code
class StudentsList extends StatelessWidget {
final List<Child> mList;
StudentsList({this.mList});
#override
Widget build(BuildContext context) {
List<Child> selectedList = [];
return Container(
margin: EdgeInsets.only(top: 50, bottom: 20),
child: ListView.builder(
shrinkWrap: true,
physics: ClampingScrollPhysics(),
itemCount: mList == null ? 0 : mList.length,
padding: EdgeInsets.only(right: 10),
itemBuilder: (BuildContext context, int position) {
return GestureDetector(
onTap: () {
if (selectedList.isEmpty) {
Navigator.push(
context,
new MaterialPageRoute(
builder: (BuildContext context) => SolokPage(
mChildList: [mList[position]],
isTeacher: true,
),
),
);
} else {
if (!selectedList.contains(mList[position])) {
selectedList.add(mList[position]);
}
Navigator.push(
context,
new MaterialPageRoute(
builder: (BuildContext context) => SolokPage(
mChildList: selectedList,
isTeacher: true,
),
),
);
}
},
child: StudentItem(
student: mList[position],
),
);
},
),
);
}
}
Stateless Widget properties are meant to be immutable
class StudentsList extends StatelessWidget {
// final means, flutter will not change value in future
final List<Child> mList;
StudentsList({this.mList});
Why ?
Because Flutter expects no business logic resides in StatelessWidget.
If we need to add new Student in Student list, it is considered as business logic.
If we need to delete some Student in Student list, it is considered as business logic.
So by using stateless widget, Flutter will only focuses on How it will be displayed on Screen, what is the width, the constraints and etc.
That's why we found final syntaxes before class properties in StatelessWidget.
Similiar to our college life. Our Grades that marked in final report, will not change even after we graduate from university. As it said to be in Final Report, then it must be final.
Stateful Widget properties are mutable
Why ?
Because flutter expects business logic resides in StatefulWidget.
Changes to be made
So I suggest to change StudentsList Widget, from this :
class StudentsList extends StatelessWidget {
final List<Child> mList; // this is the issue
StudentsList({this.mList});
to this one :
class StudentsList extends StatefulWidget {
#override
_StudentsListState createState() => _StudentsListState();
}
class _StudentsListState extends State<StudentsList> {
// final List<Child> mList; // Do not mark this as final
List<Child> mList;
...
}
Working Repository
You may look working repository that is closely-related to your issue. Github
Stateless Widgets property cannot be immutable means in simple words is that it should not contain any non-final variables.
Simply convert it to Stateful widget and inside the class _StudentsListState create your variable WITHOUT the final keyword because you are modifying the value of that List.
If you want to keep stateless (IE you just need to return some data or maybe youre using hooks) you could also try toList() to create a copy, then modify, then replace the original list
I encountered this problem in a simple function, and I solved it like this.
Future<void> createProduct({required Product product, required List<File> images}) async {
for (final image in images) {
final imageId = const Uuid().v4();
final compressedimage = await ImageCompress.instance.compressFile(image);
final taskSnapShot = await StorageService.instance.uploadProductPhoto(
file: compressedimage,
productId: product.productId,
childUUID: imageId,
);
final downloadURL = await taskSnapShot.ref.getDownloadURL();
product.imagesUrl.add(downloadURL);
// sendProduct.addImages(downloadURL: downloadURL);
}
await _collection.doc().set(
product.toMap(),
);
}
turn it into this.
Future<void> createProduct({required Product product, required List<File> images})
async {
List<String> newUrls = [];
for (final image in images) {
final imageId = const Uuid().v4();
final compressedimage = await ImageCompress.instance.compressFile(image);
final taskSnapShot = await StorageService.instance.uploadProductPhoto(
file: compressedimage,
productId: product.productId,
childUUID: imageId,
);
final downloadURL = await taskSnapShot.ref.getDownloadURL();
newUrls.add(downloadURL);
// sendProduct.addImages(downloadURL: downloadURL);
}
final sendProduct = product.copyWith(imagesUrl: newUrls );
await _collection.doc().set(
sendProduct.toMap(),
);
}
I cant seem to figure out how to get all of my items in my list to display in the list view
Currently, when I click my button to display the list, only one items shows up. If I click back, and click main button again, it shows 2 items from the list. Rinse and repeat, 3 items. I can't seem to debug with print statements to see where my error lies. When I try print(trails) or other variations, it says Instance of trail model (not very helpful). Any ideas?
Here's my code:
class HomeScreen extends State<MyApp> {
int counter = 0;
Future<List<TrailModel>> fetchData() async {
counter++;
var response = await get(
'https://www.hikingproject.com/data/get-trails?lat=39.733694&lon=-121.854771&maxDistance=10&key=200419778-6a46042e219d019001dd83b13d58aa59');
final trailModel = TrailModel.fromJson(json.decode(response.body));
//trails.add(trailModel);
setState(() {
trails.add(trailModel);
});
return trails;
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text("HikeLocator")),
body: new RaisedButton(
child: Text("click me"),
onPressed: () async {
final trails = await fetchData();
Navigator.push(
context,
new MaterialPageRoute(builder: (context) => new ListScreen(trails)),
);
},
),
));
}
}
class ListScreen extends StatelessWidget {
final List<TrailModel> trails;
ListScreen(this.trails);
#override
Widget build(BuildContext ctxt) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Here are your trails"),
),
body: TrailList(trails),
);
}
}
class TrailList extends StatelessWidget {
final List<TrailModel> trails;
TrailList(this.trails);
Widget build(context) {
return ListView.builder(
itemCount: trails.length,
itemBuilder: (context, int index) {
Object myText = json.encode(trails[index].trails);
List<dynamic> myText2 = json.decode(myText);
return Text(myText2[index]['name']);
},
);
}
}
class TrailModel {
Object trails;
TrailModel(this.trails);
TrailModel.fromJson(Map<String, dynamic> parsedJson) {
trails = parsedJson['trails'];
}
}
I think my problem might lie in the fetchData(), but I'm not entirely sure. Trying to at least print out the values to limit where my problem might be. (Is it only adding 1 to the list each time I press the button? Is it only rendering one when I click it? Is it fetching all the data each click or only one json object? etc.)
Thank you kindly for any assistance. Sorry, I'm kind of new to dart, so this is a huge learning curve for
There are a couple problems in your code. The main reason this doesn't work as you expect is because you're parsing all elements of the json into one TrailModel object, but then your code assumes that you'll have multiple TrailModel objects.
The easiest way to fix it up and get it working is to use the list from TrailModel.trails instead of the one in the widget.
First, in ListScreen, pass just the first element in the list.
class ListScreen extends StatelessWidget {
final List<TrailModel> trails;
...
#override
Widget build(BuildContext ctxt) {
return new Scaffold(
...
body: TrailList(trails.first),
);
}
}
Then, in TrailList, use the trails list you have from TrailModel:
class TrailList extends StatelessWidget {
final TrailModel model;
TrailList(this.model);
Widget build(context) {
return ListView.builder(
itemCount: model.trails.length,
itemBuilder: (context, int index) {
final trail = model.trails[index];
...
},
);
}
}
When I try print(trails) or other variations, it says Instance of trail model (not very helpful)
print uses the output of the toString method in your classes. You're seeing Instance of trail model because that's the default implementation you get from the super class Object. You can override it to get something more useful:
class TrailModel {
#override
String toString() => 'trails=$trails';
}
I am migrating my application from android to flutter and till now I have used ListView in a flutter. my question is, is there any specialized technique to handle a large amount of data in the flutter? for reference, you can look at android RecyclerView. it handles in-memory views and recycles its runtime. so how to achieve functionality like RecyclerView in Flutter? or it's not necessary for the flutter?
The easiest way is to use a ListView.builder without specifying the itemCount parameter.
Here is the simplest example:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Infinite List"),
),
body: ListView.builder(
itemBuilder: (context, index) {
return Text("$index");
},
),
);
}
}
Later, you can enhance this by fetching real data. You could show a 'CircularProgressIndicator' in the last item of the list while waiting for the new data.
body: ListView.builder(
itemBuilder: (context, index) {
if (index < data.length) {
// Show your info
return Text("$index");
} else {
getMoreData();
return Center(child: CircularProgressIndicator());
}
},
itemCount: data.length + 1,
),
You can see that we trick the list by adding an index, and calling for more data when displaying that final index.
getMoreData() would include a call to setState() to force a rebuild and to take into account the new data.
Below is a simple infinite list widget based on chemamolins's answer. It accepts an itemBuilder to build the current item and onRequest callback to request more data when the user scrolls to the bottom.
import 'package:flutter/material.dart';
typedef Future<List<T>> RequestFn<T>(int nextIndex);
typedef Widget ItemBuilder<T>(BuildContext context, T item, int index);
class InifiniteList<T> extends StatefulWidget {
final RequestFn<T> onRequest;
final ItemBuilder<T> itemBuilder;
const InifiniteList(
{Key? key, required this.onRequest, required this.itemBuilder})
: super(key: key);
#override
_InifiniteListState<T> createState() => _InifiniteListState<T>();
}
class _InifiniteListState<T> extends State<InifiniteList<T>> {
List<T> items = [];
bool end = false;
_getMoreItems() async {
final moreItems = await widget.onRequest(items.length);
if (!mounted) return;
if (moreItems.isEmpty) {
setState(() => end = true);
return;
}
setState(() => items = [...items, ...moreItems]);
}
#override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) {
if (index < items.length) {
return widget.itemBuilder(context, items[index], index);
} else if (index == items.length && end) {
return const Center(child: Text('End of list'));
} else {
_getMoreItems();
return const SizedBox(
height: 80,
child: Center(child: CircularProgressIndicator()),
);
}
},
itemCount: items.length + 1,
);
}
}
Usage
child: InifiniteList<String>(
onRequest: requestItems,
itemBuilder: (context, item, index) => Container(
padding: const EdgeInsets.all(30),
color: index % 2 == 0 ? Colors.purple.shade100 : Colors.lime.shade100,
child: Text(item, style: Theme.of(context).textTheme.headline6),
),
),
// normally this is the place where you request the next batch of items
// on the network.
Future<List<String>> requestItems(int nextIndex) {
const pageSize = 15;
var result = List<String>.generate(pageSize, (i) => "Item: ${nextIndex + i + 1}");
return Future<List<String>>.delayed(
const Duration(milliseconds: 500),
() => result,
);
}
Live Demo
Displaying lists of data is a fundamental pattern for mobile apps. Flutter includes the ListView widget to make working with lists a breeze.
I have solved the issue by doing the following steps
Use the ListView Widget
There are four constructors of ListView Class
You have to use Builder Constructor (ListView.builder)
Builder Constructor is used when you have to make a list of elements on demand
It is appropriate for list views with a large (or infinite) number of children
HERE you can have Solution Video CLICK HERE