I have seen a code from a tutorial to create a favorite selection in ListView. But I have seen it doesn't use the provider, I wonder if it is better to use it. I want to create a favorite selection by click on a button on each item and I want to retrieve the select item in an other ListView. This is the code from the tutorial :
main.dart
import 'package:badges/badges.dart';
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
import 'package:words/favorite_words_route.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Likely Words',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Likely Words'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> words = nouns.take(40).toList();
List<String> savedWords = List<String>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
actions: <Widget>[
Badge(
badgeContent: Text('${savedWords.length}'),
toAnimate: false,
position: BadgePosition.topRight(top: 0, right: 0),
child: IconButton(
icon: Icon(Icons.bookmark),
onPressed: () => pushToFavoriteWordsRoute(context),
),
),
],
),
body: ListView.separated(
itemCount: words.length,
separatorBuilder: (BuildContext context, int index) => Divider(),
itemBuilder: (BuildContext context, int index) {
String word = words[index];
bool isSaved = savedWords.contains(word);
return ListTile(
title: Text(word),
trailing: Icon(
isSaved ? Icons.favorite : Icons.favorite_border,
color: isSaved ? Colors.red : null,
),
onTap: () {
setState(() {
if (isSaved) {
savedWords.remove(word);
} else {
savedWords.add(word);
}
});
},
);
},
),
);
}
Future pushToFavoriteWordsRoute(BuildContext context) {
return Navigator.of(context).push(
MaterialPageRoute(
builder: (BuildContext context) => FavoriteWordsRoute(
favoriteItems: savedWords,
),
),
);
}
}
favorite_words_route.dart
import 'package:flutter/material.dart';
class FavoriteWordsRoute extends StatelessWidget {
final List<String> favoriteItems;
const FavoriteWordsRoute({Key key, #required this.favoriteItems}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Favorites words'),
),
body: ListView.separated(
itemCount: favoriteItems.length,
separatorBuilder: (BuildContext context, int index) => Divider(),
itemBuilder: (BuildContext context, int index) => ListTile(
title: Text(favoriteItems[index]),
),
),
);
}
}
Tell me for the performance if the provider is better.
Provider will not make the performance better.
Calling setState rebuilds a widget. If a widget is rebuilt, at-worst all of it's children are built. So if I build a list, all list-items might rebuild.
The optimization that can be made in this case is calling setState from inside the ListItem instead of the MyHomePage widget. This gives Flutter the least work to do, since ListItem has fewer children than ListView.
Provider does not affect the way Flutter builds widgets. You can still make the same mistakes of rebuilding too many widgets too frequently when using Provider.
Provider does however give you some tools to rebuild smaller widgets. One example is Selector.
Related
I'm making a Todolist as first project with flutter and now I have this error:
Problem 1
type 'int' is not a subtype of type 'String'
Problem 2
I have also problem with line: 18
in the tutorial he says: List todos = List(); but this isn't working
My code:
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
theme: ThemeData(
brightness: Brightness.light,
primaryColor: Colors.blue,
colorScheme: ColorScheme.fromSwatch().copyWith(secondary: Colors.orange)
),
home: const TodoApp()
));
class TodoApp extends StatefulWidget{
const TodoApp({Key? key}) : super(key: key);
#override
_TodoState createState() => _TodoState();
}
class _TodoState extends State<TodoApp>{
List todos = List.filled(3, 0, growable: true);
void initSate(){
super.initState();
todos.add("Item1");
todos.add("Item2");
todos.add("Item3");
todos.add("Item4");
}
#override
Widget build(BuildContext context){
return Scaffold(
appBar: AppBar(
title: const Text("TodoApp"),
),
body: ListView.builder(
itemCount: todos.length,
itemBuilder: (BuildContext context, int index){
return Dismissible(
key: Key(todos[index]),
child: Card(
child: ListTile(
title: Text(todos[index]),
),
));
}),
);
}
}
I would like to learn something.
Thank you for helping :)
Try below code hope its helpful to you. use toString() for index
ListView.builder(
itemCount: todos.length,
itemBuilder: (BuildContext context, int index) {
return Dismissible(
key: Key(
todos[index].toString(),
),
child: Card(
child: ListTile(
title: Text(
todos[index].toString(),
),
),
),
);
}),
Your result screen->
The problem is simply because you're incorrectly initialized your variables with:
List todos = List.filled(3, 0, growable: true);
which will initialized your variables with integer.
The same problem could be easily avoided in the future by creating your list with specific type, like List<String>.
So, change your code to the following:
// Using dynamic list
List todos = List.filled(3, "", growable: true);
// this will initialized todos => ["", "", ""]
or
// use list string
List<String> todos = List.filled(3, "", growable: true);
// this will initialized todos => ["", "", ""]
When removing item with dismissable, you need to add onDismissed property for Dismissable.
Here the complete working code with all the errors corrected:
import 'package:flutter/material.dart';
void main() => runApp(MaterialApp(
theme: ThemeData(
brightness: Brightness.light,
primaryColor: Colors.blue,
colorScheme:
ColorScheme.fromSwatch().copyWith(secondary: Colors.orange)),
home: const TodoApp()));
class TodoApp extends StatefulWidget {
const TodoApp({Key? key}) : super(key: key);
#override
_TodoState createState() => _TodoState();
}
class _TodoState extends State<TodoApp> {
//List<String> todos = List.filled(0, "", growable: true);
// Use empty list instead List.filled
List<String> todos = [];
// Need to override initState to adding items.
#override
void initState() {
todos.add("Item1");
todos.add("Item2");
todos.add("Item3");
todos.add("Item4");
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("TodoApp"),
),
body: ListView.builder(
itemCount: todos.length,
itemBuilder: (BuildContext context, int index) {
// use item for each todo by index.
final item = todos[index];
return Dismissible(
// Add onDismissed property to properly delete an item
onDismissed: (direction) {
// Remove the item from the data source.
setState(() {
todos.removeAt(index);
});
// Then show a snackbar.
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('$item dismissed')));
},
key: Key(item),
child: Card(
child: ListTile(
title: Text(item),
),
));
}),
);
}
}
Result:
See https://api.dart.dev/stable/2.14.4/dart-core/List/List.filled.html for details.
today I have faced a problem with streams and StreamBuilder.
The problem is the following:
If you have multiple StreamBuilder widgets listening to the same stream, and you add data into its sink, this data will go out through the stream the amount of StreamBuilder listeners that you have, in other words:
If you have one StreamController (or BehaviorSubject) , k amount of widgets of type StreamBuilder, and you try to do StreamController.sink.add(event), this event will go out k times trough the stream, one per StreamBuilder.
Is that an expected behavior (expected behavoir = input an event and listening just once from the other side independentlly of the amount of listeners) ? I was able to "fix" this encapsulating almost all the widget tree into one StreamBuilder, but this isnt as optimal as the first approach because of you are rendering the whole tree instead of some little node widgets.
Here I left some code to test it if you want (This code is a modification of flutter create project_name project).
Thank you!
(P.D: This works well if you just listen the streams without StreamBuilder, i.e: streamController.stream.listen..)
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:rxdart/subjects.dart';
class MyAppBloc with ChangeNotifier {
int _currentIndex;
BehaviorSubject<bool> _controller;
MyAppBloc() {
_currentIndex = 0;
_controller = BehaviorSubject<bool>();
}
Stream<int> get currentIndex => _controller.stream.map<int>((event) {
print('[event: $event]');
_currentIndex++;
return _currentIndex;
});
StreamSink<bool> get increment => _controller.sink;
void close() {
_controller.close();
}
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:test_project/bloc/my_app_bloc.dart';
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
Widget leadingBuilder(MyAppBloc bloc) {
return StreamBuilder<int>(
initialData: 0,
stream: bloc.currentIndex,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
print('[leadingBuilderSnapshot: $snapshot]');
return Text(snapshot.data.toString());
},
);
}
StreamBuilder<int> counterBuilder(MyAppBloc bloc) {
return StreamBuilder<int>(
initialData: 0,
stream: bloc.currentIndex,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
print('[counterBuilderSnapshot: $snapshot]');
return Text(
snapshot.data.toString(),
style: Theme.of(context).textTheme.headline4,
);
},
);
}
#override
Widget build(BuildContext context) {
print('[build]');
final _bloc = Provider.of<MyAppBloc>(context);
return Scaffold(
appBar: AppBar(
leading: Container(
width: 30,
height: 30,
alignment: Alignment.center,
child: leadingBuilder(_bloc),
),
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
StreamBuilder<int>(
initialData: 0,
stream: _bloc.currentIndex,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
return Text('${snapshot.data}');
},
),
Text(
'You have pushed the button this many times:',
),
counterBuilder(_bloc),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => _bloc.increment.add(true),
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Because currentIndex is a getter and you are using map(), a new stream will be created every time
you call bloc.currentIndex and StreamBuilder will listen to it.
So in original code , there are actually 1 StreamControlller, and k Streams. (k: number of StreamBuilder)
To solve your problem, you can create an eventController, and listen to it inside a bloc to execute your logic. (eventStream is listened from only bloc itself, it will be created just once)
for example:
class MyAppBloc {
MyAppBloc() {
_eventController.listen((event) {
print('[event: $event]');
_indexController.add(currentIndex.value + 1);
});
}
final _indexController = BehaviorSubject<int>.seeded(0);
final _eventController = PublishSubject<bool>();
ValueStream<int> get currentIndex => _indexController.stream;
StreamSink<bool> get increment => _eventController.sink;
void close() {
_indexController?.close();
_eventController?.close();
}
}
You have to use 2 streams/sink and put the increment outside the get stream.
import 'dart:async';
import 'package:rxdart/subjects.dart';
class Bloc {
int _counter = 0;
Bloc() {
_controller.stream.listen(_incrementStream);
}
final _counterStream = BehaviorSubject<int>.seeded(0);
Stream get presentCounter => _counterStream.stream;
Sink get _addValue => _counterStream.sink;
StreamController _controller = BehaviorSubject<bool>();
StreamSink<bool> get incrementCounter => _controller.sink;
void _incrementStream(data) {
_counter += 1;
_addValue.add(_counter);
}
void dispose() {
_counterStream.close();
_controller.close();
}
}
import 'package:flutter/material.dart';
import 'package:increment/bloc.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Bloc _bloc = Bloc();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
StreamBuilder<int>(
stream: _bloc.presentCounter,
builder: (context, snapshot) {
return Text(
'${snapshot.data}',
style: Theme.of(context).textTheme.headline4,
);
}),
SizedBox(
height: 60,
),
StreamBuilder<int>(
stream: _bloc.presentCounter,
builder: (context, snapshot) {
return Text(
'${snapshot.data}',
style: Theme.of(context).textTheme.headline4,
);
}),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_bloc.incrementCounter.add(true);
},
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
I need to change the ScrollPhysics for almost every scrollable widget in an app to BouncingScrollPhysics(). I have tried to find a way to do this without adding the physics property everywhere, but I haven't found good a way yet. One solution is to use flutter_platform_widgets and set initialPlatform to iOS, but that will change a lot of other things as well.
Does anyone know if this is possible, and in that case how?
You can copy paste run full code below
You can extend ScrollBehavior and put in builder of MaterialApp
In demo code, iOS, macOS, android will use BouncingScrollPhysics
code snippet
class ScrollBehaviorModified extends ScrollBehavior {
const ScrollBehaviorModified();
#override
ScrollPhysics getScrollPhysics(BuildContext context) {
switch (getPlatform(context)) {
case TargetPlatform.iOS:
case TargetPlatform.macOS:
case TargetPlatform.android:
return const BouncingScrollPhysics();
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
return const ClampingScrollPhysics();
}
return null;
}
}
...
builder: (context, widget) {
return ScrollConfiguration(
behavior: ScrollBehaviorModified(), child: widget);
},
working demo
full code
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class ScrollBehaviorModified extends ScrollBehavior {
const ScrollBehaviorModified();
#override
ScrollPhysics getScrollPhysics(BuildContext context) {
switch (getPlatform(context)) {
case TargetPlatform.iOS:
case TargetPlatform.macOS:
case TargetPlatform.android:
return const BouncingScrollPhysics();
case TargetPlatform.fuchsia:
case TargetPlatform.linux:
case TargetPlatform.windows:
return const ClampingScrollPhysics();
}
return null;
}
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
builder: (context, widget) {
return ScrollConfiguration(
behavior: ScrollBehaviorModified(), child: widget);
},
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: ListView.separated(
itemBuilder: (BuildContext context, int index) {
return Text('Item$index');
},
separatorBuilder: (BuildContext context, int index) {
return Divider();
},
itemCount: 50,
),
),
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
simple solution for your particular question is =>
inside ThemeData
platform: TargetPlatform.iOS,
Snapshot of the Theme code
I've created a list of images and I want to display them in cards layout in GridView. Also I created a constructor with properties Color backgroundColor and AssetImage assetImage. This constructor is in a different class Home() so that in my material app I can call it in "home" property. But Home() is asking for 2 arguments and I'm not able to fix this. What should I do? And yeah I'm building an all-in-one websites app so webView is also in the code.
void main() => runApp(
MaterialApp(
routes: {
"/webview": (_) => WebviewScaffold(
withJavascript: true,
withLocalStorage: true,
url: 'https://www.google.com/',
appBar: AppBar(
title: Text('Browser'),
),
),
},
home: Home(),
),
);
class Home extends StatefulWidget {
const Home(this.backgroundColor, this.assetImage);
final Color backgroundColor;
final AssetImage assetImage;
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
final webView = FlutterWebviewPlugin();
#override
void initState() {
super.initState();
webView.close();
}
#override
Widget build(BuildContext context) {
var gridView = new GridView.builder(
itemCount: 24,
gridDelegate:
new SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
itemBuilder: (BuildContext context, int index) {
return new GestureDetector(
child: Container(
padding: EdgeInsets.all(6),
child: Card(
color: widget.backgroundColor,
child: new InkWell(
onTap: () {},
child: Center(
child: Image(
image: widget.assetImage,
),
),
),
),
),
onTap: () {},
);
});
return Scaffold(
appBar: AppBar(
title: Text('ANytHiNg'),
backgroundColor: Colors.green,
),
body: gridView,
);
}
}
You can may find this helpful.
where you can pass list like this to the constructor,
void main() {
runApp(MyApp(
items: List<String>.generate(10000, (i) => "Item $i"),
));
}
after this get the list by this,
MyApp({Key key, #required this.items}) : super(key: key);
I am trying to navigate to a page called contactView. I have made a list of contacts and I wait to navogate to a contact when I click on there name. This is what I have so far. I am stuck trying to get the navigation to work. Any help would be great.
class ContactList extends StatelessWidget {
final List<Contact> _contacts;
ContactList(this._contacts);
#override
Widget build(BuildContext context) {
return new ListView.builder(
padding: new EdgeInsets.symmetric(vertical: 8.0),
itemBuilder: (context, index) {
return new _ContactListItem(_contacts[index]);
Navigator.push(context, MaterialPageRoute(builder: (context) => viewContact())
);
},
itemCount: _contacts.length,
);
}
}
Here are few things that I can immediately point out (Problems):
onPressed is not available on ListView.builder() , you may check
here:
https://docs.flutter.io/flutter/widgets/ListView/ListView.builder.html
Navigator.push(context, MaterialPageRoute(builder: (context) => viewContact()) this won't execute because it is after return
Suggestions:
You might need to wrap your _ContactListItem() inside a
GestureDetector and implement an onTap callback
Sample Code:
class ContactList extends StatelessWidget {
final List<Contact> _contacts;
ContactList(this._contacts);
#override
Widget build(BuildContext context) {
return ListView.builder(
padding: EdgeInsets.symmetric(vertical: 8.0),
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
//TODO: Insert your navigation logic here
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) =>
ContactView(_contacts[index])));
},
child: _ContactListItem(_contacts[index]),
);
},
itemCount: _contacts.length,
);
}
}
Another option could be to change the implementation of
_ContactListItem() and may be use a ListTile and implement an onTap in ListTile, you can find it here: https://docs.flutter.io/flutter/material/ListTile-class.html
You may also try to implement named routes, here is a tutorial for
that https://flutter.io/cookbook/networking/named-routes/
I hope this was helpful in someway, let me know if I misinterpreted the question.
See if the below is what you're looking for.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Contact Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Contact Demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final _contacts = [
Contact(name: 'John'),
Contact(name: 'Mary'),
Contact(name: 'Suzy')
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: null,
title: const Text(
'Contact Demo',
style: const TextStyle(color: Colors.white),
),
),
body: ListView.builder(
itemCount: _contacts.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
title: Text('Contact #$index'),
onTap: () {
Navigator.of(context).push(MaterialPageRoute<void>(
builder: (BuildContext context) =>
ContactView(contact: _contacts[index]),
));
},
);
},
),
);
}
}
class Contact {
Contact({this.name});
final String name;
}
class ContactView extends StatelessWidget {
ContactView({this.contact});
final Contact contact;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(contact.name),
),
body: Center(
child: Text(contact.name),
),
);
}
}