flutter:: How to call listview using obx? - flutter

Record.dart
class Record extends StatefulWidget {
final List<String>? records;
const Record({
Key? key,
required this.records,
}) : super(key: key);
#override
_RecordState createState() => _RecordState();
}
class _RecordState extends State<Record> {
....
#override
Widget build(BuildContext context) {
return widget.records == null ? SizedBox() : ListView.builder(
itemCount: widget.records!.length,
shrinkWrap: true,
reverse: true,
itemBuilder: (BuildContext context, int i) {
return Card(
elevation: 5,
child: ExpansionTile(
...
record_body.dart
class record_Body extends StatefulWidget {
#override
_record_Body createState() => _record_Body();
static List<String>? records;
}
class _record_Body extends State<record_Body> {
List<String>? get record => video_Body.records;
...
before
Container(
height: 300,
child: IconButton(
onPressed: (){
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Record(
records: video_Body.records,
),
),
);
},
icon: Icon(Icons.mic, color: Colors.green, size: 45)
),
),
after
body: Obx(() {
switch(RouteName.values[controller.currentIndex.value]) {
case RouteName.Record:
return Record(records: video_Body.records); // error
Originally 'Record'(in record.dart) was loaded via navigator.push.
I want to load the listview using obx instead of navigator.push.
In my after, the listview is not visible. How do I solve this?

Related

I was able to display the data in CloudFirestore, but I want to transfer the data to the other side of the screen

I have managed to acquire and display data using CloudFireStore, but I would like to send the acquired data to the destination screen when the screen is transferred.
Navigator.push(
context,
MaterialPageRoute(builder: (context) => NextPage('test')),
));
but I don't know which function to put in.
↓This code is the one that sends the data.
class Rigakubu extends StatefulWidget {
const Rigakubu({Key? key}) : super(key: key);
#override
State<Rigakubu> createState() => _RigakubuState();
}
class _RigakubuState extends State<Rigakubu> {
final _firestore = FirebaseFirestore.instance;
List<DocumentSnapshot> documentList = [];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('理学部'),),
body: SafeArea(
child: StreamBuilder(
stream: _firestore.collection('理学部').snapshots(),
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return Center(
child:Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.do_disturb_on_outlined,size: 150,),
Text(
'校外のメールアドレスでログインしているため\nこの機能は利用できません。',
style: TextStyle(fontSize: 18),
textAlign: TextAlign.center,
),
],
)
);
}
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator()
);
}
return ListView.builder(
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index){
return ListTile(
title: Text(snapshot.data!.docs[index].get('zyugyoumei')),
subtitle: Text(snapshot.data!.docs[index].get('kousimei')),
onTap: (){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => View(post: snapshot.data!.docs)),
);
}
);
}
);
},
)
),
);
}
}
↓This code is the code on the receiving end of the data.
import 'package:flutter/material.dart';
import 'package:http/http.dart';
class View extends StatefulWidget {
final int post;
View(this.post);
const View({Key? key, required this.post}) : super(key: key);
#override
State<View> createState() => _ViewState();
}
class _ViewState extends State<View> {
late int state;
#override
void initState() {
super.initState();
// 受け取ったデータを状態を管理する変数に格納
state = widget.post;
}
#override
Widget build(BuildContext context) {
return Container(
child: Text(widget.post.bumon),
);
}
}
You could probably try this inside the onTap function before calling the Navigator
documentList = snapshot.data!.docs;
But generally avoid passing data through constructor and try to work with state management.

When I add elements to listview, how to update listview in Flutter?

I am new to flutter and I would like to add element every 5 seconds to my list view. I have list view and I think I have the true adding method. However, I do not know how to update my list view every 5 seconds.
void randomCity(){
List <int> colors = [yellow,green,blue,red,black,white];
List <String> countryNames = ["Gdańsk","Warszawa","Poznań","Białystok","Wrocław","Katowice","Kraków"];
List <String> countryImages = [gdanskPic,warszawaPic,poznanPic,bialystokPic,wroclawPic,katowicePic,krakowPic];
Random random = new Random();
DateTime now = new DateTime.now();
Future.delayed(Duration(seconds: 5), (){
setState(() {
int randomCity = random.nextInt(countryNames.length);
int randomColor = random.nextInt(colors.length);
countrylist.add(Country(
countryNames[randomCity], countryImages[randomCity],
colors[randomColor], now.toString()));
});
});
}
In this code I am adding new element to my list view.
randomCity();
return Scaffold(
backgroundColor: Colors.grey[100],
appBar: AppBar(
backgroundColor: Colors.grey[100],
elevation: 0.0,
title: Text(
"Random City App",
style: TextStyle(fontSize: 20.0, color: Colors.black),
),
centerTitle: true,
actions: <Widget>[
IconButton(
icon: Icon(
Icons.add,
color: Colors.black,
size: 32,
),
onPressed: () {})
],
),
body: ListView.builder(
itemCount: countrylist.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => CountryDetails(countryName: countrylist[index].name,
appBarColor: countrylist[index].color, date: countrylist[index].date, image: countrylist[index].image,))
);
},
title: Text(countrylist[index].name + " ${countrylist[index].date}"),
tileColor: Color(countrylist[index].color),
),
);
},
));
}
And this is my ListView.Builder.
You have to convert your widget into StatefulWidget and then rebuild it with setState (more info on ways to manage state https://flutter.dev/docs/development/data-and-backend/state-mgmt/options)
class MyApp extends StatelessWidget { // your main widget
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: MyWidget(),
),
);
}
}
class MyWidget extends StatefulWidget { // create new StatefulWidget widget
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
List<Country> countrylist = []; // mover other variables in here
...
void randomCity(){
...
setState(() {}); // this will rebuild your widget again and again
}
#override
Widget build(BuildContext context) {
Future.delayed(Duration(seconds: 5), (){
randomCity();
});
return ListView.builder(
itemCount: countrylist.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
onTap: () {},
title: Text(countrylist[index]),
),
);
},
);
}
}
You have to tell the ListView to rebuild which you can do with the setState method (if you are using a StefulWidget). Also, I would use Timer instead of Future.delayed for periodic updates. Here would be a simplified example of your usecase:
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Timer timer;
final countryNames = ['Germany', 'Croatia', 'Turkey', 'USA'];
List<String> countryList = [];
#override
void initState() {
Timer.periodic(Duration(seconds: 5), (timer) {
int randomCity = Random().nextInt(countryNames.length);
countryList.add(countryNames[randomCity]);
setState(() {});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('List Updater'),
),
body: ListView.builder(
itemBuilder: (context, index) {
return Card(
child: Text(countryList[index]),
);
},
itemCount: countryList.length,
),
);
}
#override
void dispose() {
timer?.cancel();
super.dispose();
}
}

BLoC returning the same data after each event

I have a BLoC whose's job it is to provide a list of models called DrinkCard, which is my custom class. Now, my bloc needs to get the data from another cubit that stores a list of Player models. My code then fills the names of the players that are playing the game into the cards.
Here is my bloc.
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:drinkly/decks/data/repository/deck_repository.dart';
import 'package:drinkly/decks/decks.dart';
import 'package:drinkly/decks/models/card.dart';
import 'package:drinkly/game/data/repository/game_repository.dart';
import 'package:drinkly/players/players.dart';
import 'package:equatable/equatable.dart';
part 'game_event.dart';
part 'game_state.dart';
class GameBloc extends Bloc<GameEvent, GameState> {
GameBloc({required this.repository})
: super(GameInitial());
final GameRepository repository;
List<DrinkCard> _getCards(DeckType deckId, List<Player> players) {
final allCards = DeckRepository.getDeckById(deckId).cards;
final processedCards =
DeckRepository.prepareCards(allCards, playerCubit.state);
return processedCards;
}
#override
Stream<GameState> mapEventToState(
GameEvent event,
) async* {
if (event is GamePrepare) {
yield GameLoaded(cards: _getCards(event.deck, event.players));
}
}
}
Here is my UI implementation, I know it's chunky but I plan to refactor it after I get a MVP working.
import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:auto_route/auto_route.dart';
import 'package:awesome_dialog/awesome_dialog.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:tcard/tcard.dart';
import '../../app/injection_container.dart';
import '../../decks/decks.dart';
import '../../players/players.dart';
import '../bloc/game_bloc.dart';
import 'helper/build_cards.dart';
var selectedDeck;
class GamesScreen extends StatelessWidget {
const GamesScreen({Key? key, required this.deck}) : super(key: key);
final DeckType deck;
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<GameBloc>(
create: (ctx) => sl<GameBloc>(),
),
BlocProvider<PlayerCubit>(
create: (ctx) => sl<PlayerCubit>(),
)
],
child: BlocListener<PlayerCubit, List<Player>>(
listener: (context, state) {
context.read<GameBloc>().add(GamePrepare(deck: deck, players: state));
},
child: GamesView(
deck: deck,
),
),
);
}
}
class GamesView extends StatefulWidget {
const GamesView({Key? key, required this.deck}) : super(key: key);
final DeckType deck;
#override
_GamesViewState createState() => _GamesViewState();
}
class _GamesViewState extends State<GamesView> {
#override
Widget build(BuildContext context) {
final height = MediaQuery.of(context).size.height;
selectedDeck = widget.deck;
return Scaffold(
backgroundColor: const Color(0xff2a2438),
body: GameBody(
deck: widget.deck,
),
);
}
}
class GameBody extends StatefulWidget {
const GameBody({
Key? key,
required this.deck,
}) : super(key: key);
final DeckType deck;
#override
_GameBodyState createState() => _GameBodyState();
}
class _GameBodyState extends State<GameBody> {
final _controller = TCardController();
var frontCardIndex = 0;
void callback(int index) {
setState(() {
frontCardIndex = index;
});
}
#override
Widget build(BuildContext context) {
final height = MediaQuery.of(context).size.height;
var players = context.read<PlayerCubit>().state;
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: CardStack(
controller: _controller,
callback: callback,
index: frontCardIndex,
deck: widget.deck,
),
),
Expanded(
child: SafeArea(
child: Align(
child: Padding(
padding: const EdgeInsets.fromLTRB(25, 0, 0, 0),
child: BlocBuilder<GameBloc, GameState>(
builder: (context, state) {
if (state is GameLoaded) {
return IconButton(
onPressed: () async {
await showModalBottomSheet(
context: context,
builder: (ctx) {
return BlocProvider(
create: (context) => sl<GameBloc>(),
child: ModalSheetBody(
height: height,
controller: _controller,
index: _controller.index,
callback: () {
var cards = CardHelper.buildCardItems(
state.cards.sublist(_controller.index),
context,
);
setState(() {
_controller.reset(
cards: cards,
);
});
},
),
);
},
);
},
icon: const Icon(
CupertinoIcons.person_add_solid,
size: 34,
),
);
}
return Container();
},
),
),
),
),
),
],
);
}
}
class ModalSheetBody extends StatelessWidget {
ModalSheetBody({
Key? key,
required this.height,
required TCardController controller,
required this.index,
required this.callback,
}) : _controller = controller,
super(key: key);
final double height;
final TCardController _controller;
final int index;
final Function callback;
#override
Widget build(BuildContext context) {
return StatefulBuilder(
builder: (ctx, stateSetter) {
var players = ctx.watch<GameBloc>().playerCubit.state;
return Container(
height: height * 0.4,
),
child: Column(
children: [
name != null
? stateSetter(
() {
context.read<GameBloc>().playerCubit.state.add(
Player(
name: name[0],
),
);
context.read<GameBloc>().add(
GameReloaded(
deck: selectedDeck,
),
);
callback();
},
)
: DoNothingAction();
},
),
],
),
Expanded(
child: GridView.builder(
itemCount: players.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
),
itemBuilder: (ctx, ind) {
return Chip(
key: UniqueKey(),
onDeleted: () {
stateSetter(() {
context.read<GameBloc>().playerCubit.removePlayer(
name: players[ind].name,
);
});
context.read<GameBloc>().add(
GameReloaded(
deck: selectedDeck,
),
);
callback();
},
deleteIcon: const Icon(
CupertinoIcons.delete,
size: 12,
),
label: Text(
players[ind].name,
style: GoogleFonts.poppins(
fontSize: height * 0.02,
color: Colors.white.withOpacity(0.65),
fontWeight: FontWeight.w500,
),
),
);
},
),
),
],
),
);
},
);
}
}
class CardStack extends StatefulWidget {
const CardStack({
Key? key,
required this.callback,
required TCardController controller,
required this.index,
required this.deck,
}) : _controller = controller,
super(key: key);
final TCardController _controller;
final Function callback;
final int index;
final DeckType deck;
#override
_CardStackState createState() => _CardStackState();
}
class _CardStackState extends State<CardStack> {
#override
Widget build(BuildContext context) {
return BlocBuilder<GameBloc, GameState>(
builder: (context, state) {
print(state.toString());
if (state is GameLoaded) {
return buildCardWidget(context, state);
}
context.read<GameBloc>().add(
GamePrepare(
deck: widget.deck,
players: [
Player(name: 'a'), // temporary initializer.
Player(name: 'b'),
],
),
);
return Container();
},
);
}
Widget buildCardWidget(BuildContext context, GameLoaded state) {
return TCard(
controller: widget._controller,
cards: [...CardHelper.buildCardItems(state.cards, context)],
);
}
}
Now, whenever a player is added, an event gets added to the GameBloc, which is supposed to cause a rebuild in the BlocProvider, and it does. But the list of DrinkCard is always the same, even though I shuffle the cards and there is a zero-chance that the bloc would return a list of the same 25 cards each time.

Flutter AnimatedList with Provider Pattern

I have model which implements ChangeNotifier
class DataModel with ChangeNotifier{
List<Data> data = List<Data>();
void addData(Data data){
data.add(data);
notifyListeners();
}
}
and a ListView which listens to those changes:
class DataListView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<DataModel>(
builder: (context, model, child) {
return ListView.builder(
itemCount: model.data.length,
itemBuilder: (context, index) {
return Text(model.data[index].value);
},
);
},
);
}
}
so far so good, when an item is added to the list in the model, the change notification triggers a rebuild of the Listview and I see the new data. But I cant wrap my head around using this with a AnimatedList instead of a ListView. Preferably id like to keep my model as it is, seeing as the animation is a concern of the ui and not of my logic.
The changenotifier always gives me a uptodate version of my data, but what i really need is a "item added" or "item removed" notification.
Is there a best practice way of doing this?
This is the result of my trial.
It's a riverpod version, but I think it's the same for providers.
There are two points.
Initialize the state in the parent widget of the widget that uses
AnimatedList.
Add / delete AnimatedList and add / delete states asynchronously by using async.
main.dart
import 'package:animatedlist_riverpod_sample/provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:hooks_riverpod/all.dart';
void main() {
runApp(ProviderScope(child: 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: Home(),
);
}
}
class Home extends HookWidget {
const Home({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final todoList = useProvider(todoListProvider.state);
return Scaffold(appBar: AppBar(title: Text('Todo[${todoList.length}]')), body: TodoListView());
}
}
class TodoListView extends HookWidget {
TodoListView({Key key}) : super(key: key);
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
final todoList = useProvider(todoListProvider.state);
#override
Widget build(BuildContext context) {
return AnimatedList(
key: _listKey,
initialItemCount: todoList.length,
itemBuilder: (context, index, animation) =>
_buildItem(todoList[index], animation, index, context),
);
}
Slidable _buildItem(Todo todo, Animation<double> animation, int index, BuildContext context) {
return Slidable(
actionPane: SlidableDrawerActionPane(),
child: SizeTransition(
sizeFactor: animation,
axis: Axis.vertical,
child: ListTile(title: Text(todo.description), subtitle: Text(todo.id), onTap: () => {})),
secondaryActions: <Widget>[
IconSlideAction(
caption: 'Delete',
color: Colors.red,
icon: Icons.delete,
onTap: () {
_listKey.currentState.removeItem(
index, (context, animation) => _buildItem(todo, animation, index, context),
duration: Duration(milliseconds: 200));
_removeItem(context, todo);
},
),
],
);
}
void _removeItem(BuildContext context, Todo todo) async {
await Future.delayed(
Duration(milliseconds: 200), () => context.read(todoListProvider).remove(todo));
}
}
provider.dart
import 'package:hooks_riverpod/all.dart';
final todoListProvider = StateNotifierProvider<TodoList>((ref) {
return TodoList([
Todo(id: '0', description: 'Todo1'),
Todo(id: '1', description: 'Todo2'),
Todo(id: '2', description: 'Todo3'),
]);
});
class Todo {
Todo({
this.id,
this.description,
});
final String id;
final String description;
}
class TodoList extends StateNotifier<List<Todo>> {
TodoList([List<Todo> initialTodos]) : super(initialTodos ?? []);
void add(String description) {
state = [
...state,
Todo(description: description),
];
}
void remove(Todo target) {
state = state.where((todo) => todo.id != target.id).toList();
}
}
sample repository is here.
I recently started to learn Flutter and was surprised to find that this topic isn't covered properly anywhere. I came up with two approaches which I called Basic and Advanced. Let's start from Basic. It's named like that because Provider is called within the same widget where AnimatedList is built.
class Users extends ChangeNotifier {
final _list = ['0', '1', '2', '3', '4'];
int get length => _list.length;
operator [](index) => _list[index];
int add() {
final int index = length;
_list.add('$index');
notifyListeners();
return index;
}
String removeAt(int index) {
String user = _list.removeAt(index);
notifyListeners();
return user;
}
}
class BasicApp extends StatelessWidget {
const BasicApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: ChangeNotifierProvider(create: (_) => Users(), child: AnimatedListDemo()));
}
}
class AnimatedListDemo extends StatelessWidget {
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
AnimatedListDemo({Key? key}) : super(key: key);
void addUser(Users users) {
final int index = users.add();
_listKey.currentState!.insertItem(index, duration: const Duration(seconds: 1));
}
void deleteUser(Users users, int index) {
String user = users.removeAt(index);
_listKey.currentState!.removeItem(
index,
(context, animation) {
return SizeTransition(sizeFactor: animation, child: _buildItem(users, user));
},
duration: const Duration(seconds: 1),
);
}
Widget _buildItem(Users users, String user, [int? removeIndex]) {
return ListTile(
key: ValueKey<String>(user),
title: Text(user),
leading: const CircleAvatar(
child: Icon(Icons.person),
),
trailing: (removeIndex != null)
? IconButton(
icon: const Icon(Icons.delete),
onPressed: () => deleteUser(users, removeIndex),
)
: null,
);
}
#override
Widget build(BuildContext context) {
Users users = Provider.of<Users>(context, listen: false);
return Scaffold(
appBar: AppBar(
title: const Text('Basic AnimatedList Provider Demo'),
),
body: AnimatedList(
key: _listKey,
initialItemCount: users.length,
itemBuilder: (context, index, animation) {
return FadeTransition(
opacity: animation,
child: _buildItem(users, users[index], index),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => addUser(users),
tooltip: 'Add an item',
child: const Icon(Icons.add),
),
);
}
}
Advanced approach differs in that it encapsulates AnimatedListState. I took this idea from Flutter's AnimatedList docs.
typedef RemovedItemBuilder = Widget Function(
String user, BuildContext context, Animation<double> animation);
class Users extends ChangeNotifier {
final _list = ['0', '1', '2', '3', '4'];
final GlobalKey<AnimatedListState> _listKey = GlobalKey();
final RemovedItemBuilder _removedItemBuilder;
Users(this._removedItemBuilder);
int get length => _list.length;
operator [](index) => _list[index];
GlobalKey<AnimatedListState> get listKey => _listKey;
int add() {
final int index = length;
_list.add('$index');
_listKey.currentState!.insertItem(index, duration: const Duration(seconds: 1));
notifyListeners();
return index;
}
String removeAt(int index) {
String user = _list.removeAt(index);
_listKey.currentState!.removeItem(
index,
(BuildContext context, Animation<double> animation) {
return _removedItemBuilder(user, context, animation);
},
duration: const Duration(seconds: 1),
);
notifyListeners();
return user;
}
}
class AdvancedApp extends StatelessWidget {
const AdvancedApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const MaterialApp(home: AnimatedListDemo());
}
}
class AnimatedListDemo extends StatelessWidget {
const AnimatedListDemo({Key? key}) : super(key: key);
Widget _buildItem(BuildContext context, String user, [int? removeIndex]) {
Users users = Provider.of<Users>(context, listen: false);
return ListTile(
key: ValueKey<String>(user),
title: Text(user),
leading: const CircleAvatar(
child: Icon(Icons.person),
),
trailing: (removeIndex != null)
? IconButton(
icon: const Icon(Icons.delete),
onPressed: () => users.removeAt(removeIndex),
)
: null,
);
}
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(create: (_) => Users((user, context, animation) {
return SizeTransition(sizeFactor: animation, child: _buildItem(context, user));
}), child: Scaffold(
appBar: AppBar(
title: const Text('Advanced AnimatedList Provider Demo'),
),
body: Consumer<Users>(builder: (BuildContext context, Users users, _){
return AnimatedList(
key: users.listKey,
shrinkWrap: true,
initialItemCount: users.length,
itemBuilder: (context, index, animation) {
return FadeTransition(
opacity: animation,
child: _buildItem(context, users[index], index),
);
},
);
}),
floatingActionButton: const AddButtonSeparateWidget(),
));
}
}
class AddButtonSeparateWidget extends StatelessWidget {
const AddButtonSeparateWidget({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
Users users = Provider.of<Users>(context, listen: false);
return FloatingActionButton(
onPressed: users.add,
tooltip: 'Add an item',
child: const Icon(Icons.add),
);
}
}
All code is published on Github. Now I want to elaborate a bit on your proposition of having "item added" or "item removed" notifications. From what I understand it goes against Flutter's philosophy where widget is a UI config. When a widget's state changes, Flutter diffs against its previous state and magically applies the diff to UI. That's why I didn't use "item added", "item removed" notifications in my implementations. However I think it should be possible to do because I saw a similar approach in Firestore subscription to document changes although for now I can't figure how to implement the same with Provider. Provider's documentation is kind of poor. After a careful reading I can't say how to implement partial updates with Provider. May be ProxyProvider with its update could help or may be ListenableProvider. Let me know if you could find the solution to your proposition.

How to deselect the already selected item after tap on another item ListView in Flutter?

I would like to allow user to select only one option from the list, if he select one after another, then only last option should be considered as selected.
In current code, user can select multiple option from the list which i want to avoid.
Tried code:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Test App',
theme: new ThemeData(
primarySwatch: Colors.red,
),
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<int> indexList = new List();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Selected ${indexList.length} ' + indexList.toString()),
),
body: new ListView.builder(
itemCount: 3,
itemBuilder: (context, index) {
return new CustomWidget(
index: index,
callback: () {
if (indexList.isNotEmpty) {
indexList.clear();
}
},
);
},
),
);
}
}
class CustomWidget extends StatefulWidget {
final int index;
final VoidCallback callback;
const CustomWidget({Key key, this.index, this.callback}) : super(key: key);
#override
_CustomWidgetState createState() => new _CustomWidgetState();
}
class _CustomWidgetState extends State<CustomWidget> {
bool selected = false;
CustomWidget lastSelectedWidget;
#override
Widget build(BuildContext context) {
return new GestureDetector(
onTap: () {
setState(() {
lastSelectedWidget = widget;
print(lastSelectedWidget.key);
selected = !selected;
});
widget.callback();
},
child: new Container(
margin: new EdgeInsets.all(5.0),
child: new ListTile(
title: new Text("Title ${widget.index}"),
subtitle: new Text("Description ${widget.index}"),
),
decoration: selected
? new BoxDecoration(color: Colors.black38, border: new Border.all(color: Colors.black))
: new BoxDecoration(),
),
);
}
}
I am implementing kind of radio button in list style.
You cannot assign the responsibility of defining which CustomWidget is selected to the own CustomWidget. A CustomWidget must not know about the existence of other CustomWidgets, neither anything about the information they hold.
Given that, your CustomWidget should be something like this:
class CustomWidget extends StatefulWidget {
final int index;
final bool isSelected;
final VoidCallback onSelect;
const CustomWidget({
Key key,
#required this.index,
#required this.isSelected,
#required this.onSelect,
}) : assert(index != null),
assert(isSelected != null),
assert(onSelect != null),
super(key: key);
#override
_CustomWidgetState createState() => _CustomWidgetState();
}
class _CustomWidgetState extends State<CustomWidget> {
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: widget.onSelect,
child: Container(
margin: EdgeInsets.all(5.0),
child: ListTile(
title: Text("Title ${widget.index}"),
subtitle: Text("Description ${widget.index}"),
),
decoration: widget.isSelected
? BoxDecoration(color: Colors.black38, border: Border.all(color: Colors.black))
: BoxDecoration(),
),
);
}
}
And the widget that uses CustomWidget:
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int currentSelectedIndex;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Selected index is $currentSelectedIndex'),
),
body: ListView.builder(
itemCount: 3,
itemBuilder: (context, index) {
return CustomWidget(
index: index,
isSelected: currentSelectedIndex == index,
onSelect: () {
setState(() {
currentSelectedIndex = index;
});
},
);
},
),
);
}
}