Having problem w/ flutter mobx RadioListTile - flutter

I'm having problems with mobx and radiobox: screen don't update when selected. I think it's a silly mistake, here are my main.dart, teste_store.dart and pubspec.yaml. The partial file .g was generated with build_runner and mobx_codegen.
A message appears when I run it: "No observables detected in the build method of Observer". I thought testeStore.selected was an observable and when changes triggers Observer to rebuild.
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:teste_flutter/teste_store.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
TesteStore testeStore = TesteStore();
#override
Widget build(BuildContext context) {
List<String> options = ["Option 1", "Option 2", "Option 3"];
return Scaffold(
appBar: AppBar(
title: Text("Test Flutter"),
),
body: Center(
child: Observer(
builder: (_){
return ListView.builder(
itemCount: options.length,
itemBuilder: (context, index) {
return RadioListTile<int>(
title: Text(options[index]),
value: index,
groupValue: testeStore.selected,
onChanged: testeStore.changeSelected,
);
},
);
}
)
),
);
}
}
teste_store.dart
import 'package:mobx/mobx.dart';
part 'teste_store.g.dart';
class TesteStore = _TesteStore with _$TesteStore;
abstract class _TesteStore with Store {
#observable
int selected = 0;
#action
void changeSelected(int newSelected) {
selected = newSelected;
}
}
pubspec.yaml
name: teste_flutter
description: A new Flutter application.
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: ">=2.7.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.0
mobx: ^1.2.1+2
flutter_mobx: ^1.1.0+2
provider: ^4.3.2+2
dev_dependencies:
flutter_test:
sdk: flutter
build_resolvers: ^1.3.10
mobx_codegen: ^1.1.0+1
build_runner: ^1.10.2
flutter:
uses-material-design: true
Edit 1 and 2:
I put the solution I found here and I shouldn't. Writing down in an answer box.

Talking to #BambinoUA, we found out a solution, just to add insight why his solution works.
I did put List view inside a Column with a sibling text widget with testeStore.selected like a text, now everything updates.
child: Observer(
builder: (_){
return Column(
children: [
Text(testeStore.selected.toString()),
ListView.builder(
itemCount: options.length,
itemBuilder: (context, index) {
return RadioListTile<int>(
title: Text(options[index]),
value: index,
groupValue: testeStore.selected,
onChanged: testeStore.changeSelected,
);
},
),
],
);
}
)
Back to original code I tried to only use print(testeStore.selected); before return inside builder of the Observer and it worked:
child: Observer(
builder: (_){
print(testeStore.selected);
return ListView.builder(
When I put inside the itemBuilder from RadioListTile don't work.
child: Observer(
builder: (_){
return ListView.builder(
itemCount: options.length,
itemBuilder: (context, index) {
print(testeStore.selected);
return RadioListTile<int>(
I assume Observer don't look changes inside other widgets builder.

I see that your TesteStore class is derived from private _TesteStore class. Maybe this is the case? And the error message looks reasonable. Try to make class with #observable public and re-build part file.
Update
Try to do intermediate assignments in Observer builder
final selected = testeStore.selected;
and then use new local variable selected inside inner builder.
Try to use ListView instead ListView.builder.
Try wrap with Observer not the List but RadioListTile.

Related

Wrap.builder like in ListView.builder

I want to use Wrap on large amount of objects.
I tried to just map all object to children but it cause serious performance issue.
I want some alternative way to build only currently displayed widgets but with style of Wrap.
Some code:
Wrap(
children: list.map(createCardFromData), // List contains 20'000 items
);
This is really good example flutter is missing in my opinion.
This is also something which is being discussed on Flutter's repo
https://github.com/flutter/flutter/issues/97544
Unfortunately, it will take some time for it to be in stable release. For the time being, I would suggest to paginate the data into chunks of maybe 100 items. There are other ways as well which might involve a lot of calculations. Meanwhile, I (or maybe some other person) could try to come up with a efficient solution and maybe contribute to the Flutter.
After some fiddling, I could build sample app per your requirement.
pubspec.yaml
name: scrollable_wrap
description: A new Flutter project.
publish_to: "none"
version: 1.0.0+1
environment:
sdk: ">=2.18.4 <3.0.0"
dependencies:
cupertino_icons: ^1.0.2
flutter:
sdk: flutter
flutter_svg: ^1.1.6
random_avatar: ^0.0.7
random_words: ^1.0.2
dynamic_layouts:
git:
url: git#github.com:flutter/packages.git
path: packages/dynamic_layouts
dev_dependencies:
flutter_lints: ^2.0.0
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
main.dart
import 'package:dynamic_layouts/dynamic_layouts.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:random_avatar/random_avatar.dart';
import 'package:random_words/random_words.dart';
class Item {
final String label;
final String avatar;
Item(this.label) : avatar = randomAvatarString(label);
}
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Scrollable Wrap',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<Item> data = [];
int n = 100000;
void init() async {
data = generateNoun().take(n).map((e) => Item(e.asString)).toList();
setState(() {});
}
#override
void initState() {
super.initState();
init();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Scrollable Wrap'),
),
body: CustomScrollView(slivers: [
DynamicSliverGrid(
gridDelegate: const SliverGridDelegateWithWrapping(
mainAxisSpacing: 0,
crossAxisSpacing: 0,
childCrossAxisExtent: double.infinity,
childMainAxisExtent: double.infinity,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
if (kDebugMode) {
print('build called for $index');
}
final item = data[index];
return Chip(
key: ValueKey(item),
label: Text('$index ${item.label}'),
avatar: SvgPicture.string(item.avatar),
);
},
childCount: data.length,
),
),
]),
);
}
}
Output
Please use flutter run --no-sound-null-safety as one of the library is not null-safe. Also, you might find lag as lots of svgs are being processed on scroll. It might not happen in production.
Do you need this many items loaded at once? You're probably using a scroll view anyway, so the user only initially sees a small batch of items rather than all of them, until they actually scroll for more. What you're looking for is lazy loading, maybe combined with a technique like infinite scroll.
Try a ListView (specifically, ListView.builder) where each item contains a Wrap widget (say, each 10 items - but you may want to experiment with this number until you see a balance between performance and visual appeal).
Or, alternatively, you may code your own Wrap that does loading lazily and reuses its views, so that it only loads and displays a couple of its children as needed, not thousands at once.
If your items are fixed width you could try something like this:
import 'dart:math';
import 'package:flutter/cupertino.dart';
typedef ValueWidgetBuilder<T> = Widget Function(T value);
class WrapBuilder extends StatelessWidget {
final double itemWidth;
final List items;
final ValueWidgetBuilder itemBuilder;
const WrapBuilder(
{Key? key,
required this.itemWidth,
required this.items,
required this.itemBuilder})
: super(key: key);
#override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
var cardsPerRow = max(1, constraints.maxWidth ~/ itemWidth);
return ListView.builder(
shrinkWrap: true,
controller: ScrollController(),
itemCount: (items.length / cardsPerRow).ceil(),
itemBuilder: (BuildContext context, int index) {
var rowItems = items.sublist(cardsPerRow * index,
min(cardsPerRow * (index + 1), items.length));
return Row(children: [
for (final item in rowItems)
SizedBox(
width: itemWidth,
child: itemBuilder(item))
]);
},
);
});
}
}
And then use like
WrapBuilder(
itemWidth: 100, //example
items: list,
itemBuilder: createCardFromData);

Trying to build Listview but I can't add any method to the Expansion tile

I have the following code to build a listview from local JSON file and it works perfectly fine. However, when I try to add a method such as onTap: (){} to the ExpansionTile in the _buildList Widget I got the following error
Error: No named parameter with the name 'onTap'. onTap: (){}, ^^^^^ /C:/src/flutter/packages/flutter/lib/src/material/expansion_tile.dart:51:9: Context: Found this candidate, but the arguments don't match. const ExpansionTile({ ^^^^^^^^^^^^^
The code in Main.dart is
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'datamodel.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Future<List<Menu>> ReadJsonData() async {
//read json file
final jsondata = await rootBundle.loadString('assets/data0.json');
//decode json data as list
final list = json.decode(jsondata) as List<dynamic>;
//map json and initialize using Model
return list.map((e) => Menu.fromJson(e)).toList();
}
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home:Scaffold(
appBar: AppBar(
title: const Text('My Title'),
),
body: FutureBuilder(
future: ReadJsonData(),
builder: (context,data){
if(data.hasError){
return Center(child: Text("${data.error}"));
}else if(data.hasData){
var items =data.data as List<Menu>;
return ListView.builder(
itemCount: items.length,
itemBuilder: (BuildContext context, int index) =>
_buildList(items[index]),
);
}else{
return Center(child: CircularProgressIndicator(),);
}
},
)
)
);
}
Widget _buildList(Menu list) {
return ExpansionTile(
leading: Icon(list.icon),
// line causing error
onTap: (){},
title: Text(
list.name!,// I added !
style: TextStyle(fontSize: list.font?.toDouble(), fontWeight: FontWeight.bold),
),
children: list.subMenu!.map(_buildList).toList(),// I added !
);
}
}
So is there any way to add the Method for each Expansion tile?
Thank you in advance!
ExpansionTile does not have an onTap property. This is because it has a default behaviour on tapping, expands or collapses.
If you'd like to execute some specific logic on expanded or collapsed, you can use onExpansionChanged:
return ExpansionTile(
onExpansionChanged: (bool expanded) {
// do what you want
},
);

Provider: setState() or markNeedsBuild() called during build

I'm using Hive to store the whole list of Card items, related to an Extension. In the screen of the selected extension, I am displaying the Cards of this extension, with bunch of filters/sorting.
To do that, I use ValueListenableBuilder to get the current extension and their cards.
And when I filter/sort this list, I want to store them into my Provider class, because I would to reuse this list into another screen.
But we I do context.read<ShowingCardsProvider>().setAll(sortedList), I got this error:
setState() or markNeedsBuild() called during build.
I don't listen ShowingCardsProvider anywhere for now.
Can you explain to me what's wrong here?
Thank you!
main.dart
runApp(MultiProvider(
providers: [
ChangeNotifierProvider.value(value: AuthProvider()),
ChangeNotifierProvider.value(value: UserProvider()),
ChangeNotifierProvider.value(value: ShowingCardsProvider()),
],
child: MyApp(),
));
showing_cards_provider.dart
import 'dart:collection';
import 'package:flutter/material.dart';
import 'package:pokecollect/card/models/card.dart';
class CardsProvider extends ChangeNotifier {
final List<CardModel> _items = [];
UnmodifiableListView<CardModel> get items => UnmodifiableListView(_items);
void setAll(List<CardModel>? items) {
_items.clear();
if (items != null || items!.isNotEmpty) {
_items.addAll(items);
}
notifyListeners();
}
void addAll(List<CardModel> items) {
_items.addAll(items);
notifyListeners();
}
void removeAll() {
_items.clear();
notifyListeners();
}
}
extension_page.dart
#override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
body: SafeArea(
child: LayoutBuilder(builder: (context, constraints) {
return CustomScrollView(
slivers: [
SliverAppBar(
...
),
...
SliverPadding(
padding: EdgeInsets.symmetric(horizontal: 16.0),
sliver: ValueListenableBuilder(
valueListenable: ExtensionBox.box.listenable(keys: [_extensionUuid]),
builder: (ctx, Box<Extension> box, child) {
List<CardModel>? cardsList = box
.get(_uuid)!
.cards
?.where((card) => _isCardPassFilters(card))
.cast<CardModel>()
.toList();
var sortedList = _simpleSortCards(cardsList);
context.read<ShowingCardsProvider>().setAll(sortedList);
return _buildCardListGrid(sortedList);
},
),
),
]
);
}),
),
);
}
This is indeed because I'm using ValueListenableBuilder and, while the Widget is building, notifyListeners is called (into my provider).
My hotfix is to do this:
return Future.delayed(
Duration(milliseconds: 1),
() => context.read<ShowingCardsProvider>().setAll(list),
);
Not very beautiful, but it works 😅
If you have a more elegant way, fell free to comment it!

widget.something in stateful widget

import 'package:duck/MoreAboutProduct.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class CategoryComponents extends StatelessWidget {
final String NAME;
CategoryComponents(
this.NAME);
#override
Widget build(BuildContext context) {
return Column(
children:[
Text($NAME),
RaisedButton(
child : Text(More Info),
onPressed:(){
Navigator.of(context).push(MaterialPageRoute(builder: (context) {
return MoreAboutProduct(
NAMEp: NAME,
}
),
],);
I have this class named CategoryComponents, it has only 1 variable in this example 'NAME', and other classes named Samsung and MoreAboutProduct, I will add the code of others in the following lines, I wanted to call CategoryComponents class in Samsung and pass the value of NAME and other values (not showing other values in this example for simplicity),I wanted to show the value of NAME in Samsung class and their will be a bottom in CategoryComponents class which transfer me to MoreAboutProduct class and show more info about the product but some data has to be for first product only that's why i need connection between Samsung and MoreAboutProduct, I found a way to do it but i really have some issues understanding the code specially the Navigator part and how does a variable in CategoryComponents that is passed in the constructor's parameter in Samsung class can be accessed in MoreAboutProduct class? and the widget.NAME part, I know it's pretty long question but please if you want to down vote it give me an answer first.
import 'package:duck/MoreAboutProduct.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'CategoryComponents.dart';
class Samsung extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
key: _x,
appBar: myAppbar("Samsung"),
body: FutureBuilder(
future: getData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return CategoryComponents(
snapshot.data[index]['NAME']
)};
This is Samsung class, their are other parameters i'm passing but only one of them in this example just making it simple so you understand my quesiton, also that's why i use ListView.builder.
import 'package:duck/myAppbar.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'myCarousel.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class MoreAboutProduct extends StatefulWidget {
final String NAMEp;
MoreAboutProduct(
{this.NAMEp});
}
#override
_MoreAboutProductState createState() => _MoreAboutProductState();
}
class _MoreAboutProductState extends State<MoreAboutProduct> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: ...,
body: Column(
children: [
...
),
Expanded(
child: Container(
child: ListView.builder(
itemCount: widget.snapshotLengthp,
itemBuilder: (context, index) {
return Container(
child: Column(
children: [
SpecificationsDetails(context, 'NAME', widget.NAMEp),
....
],
),);},),),),),);
SpecificationsDetails is a Function responsible for the styling, no need to show it.
here are some sample runs :
This image is in Samsung class but Categorycomponents class is responsible for passing data to it and the styling
And this is in MoreAboutProduct class
You can create named routes in your app.
MaterialApp(
// Start the app with the "/" named route. In this case, the app starts
initialRoute: '/',
routes: {
'/': (context) => HomePage(),
'/more_about_products': (context) => MoreAboutProduct(),
'/samsung': (context) => Samsung(),
},
);
code from flutter docs.
In category components class you can have something like this:
Navigator.of(context).pushNamed("/more_about_products", arguments = {name: this.NAME});
after that you can grab this argument inside the MoreAboutProducts with ModalRoute like this:
class _MoreAboutProductState extends State<MoreAboutProduct> {
#override
Widget build(BuildContext context) {
final args = ModalRoute.of(context).settings.arguments;
// to grab the **name** argument use ***args.name*** like in print function below.
print(args.name)
return Scaffold(
appBar: ...,
body: Column(
children: [
...
),
Expanded(
child: Container(...etc code....
Please read the comment beetween print function and ModalRoute.
for more info view flutter named routes and passing the arguments to named routes

Error: The getter 'length' was called on null

Error says:
NoSuchMethodError: The getter 'length' was called on null
It is a basic flutter music player App.
main.dart
import 'package:flute_music_player/flute_music_player.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
List<Song> _songs;
#override
void initState() {
// TODO: implement initState
super.initState();
initPlayer();
}
void initPlayer() async{
var songs = await MusicFinder.allSongs();
songs=new List.from(songs);
setState(() {
_songs = songs;
});
}
#override
Widget build(BuildContext context) {
Widget home(){
new Scaffold(
appBar: new AppBar(title: new Text("Music App"),
),
body: new ListView.builder(
itemCount: _songs.length,
itemBuilder: (context,int index){
return new ListTile(
leading: new CircleAvatar(
child: new Text(_songs[index].title[0]),
),
title: new Text(_songs[index].title),
);
}),
);
}
return new MaterialApp(
home: home(),
);
}
}
pubspec.yaml
name: music_player
description: A new Flutter application.
version: 1.0.0+1
environment:
sdk: ">=2.1.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^0.1.2
flute_music_player:
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
It should show the list of musics as result but gives an unexpected error.I am running on android.Plz help me out.
It should show the list of musics as result but gives an unexpected error.I am running on android.Plz help me out.
Change itemCount: _songs.length to itemCount: _songs?.length ?? 0 - it helps to avoid exception
Since you're performing an async operation, that would take certain amount of time so when your app first builds, the songs array is null. Try to start with an empty array instead of a null array: List<Song> _songs = []; then, when the async operation is completed the setState will make the widget to rebuild and show the array with data.