Skip to content

Instantly share code, notes, and snippets.

@shriharip
Created November 17, 2021 13:06
Show Gist options
  • Save shriharip/c8fdb38d8ab42ad0d23d86f2ada4af12 to your computer and use it in GitHub Desktop.
Save shriharip/c8fdb38d8ab42ad0d23d86f2ada4af12 to your computer and use it in GitHub Desktop.
listview flutter with out rebuilding the complete list (with null safety)
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _navigatorKey = GlobalKey<NavigatorState>();
FakeApi? _api;
@override
void initState() {
_api = FakeApi(_navigatorKey);
super.initState();
}
@override
void dispose() {
_api?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => MaterialApp(
navigatorKey: _navigatorKey,
home: MyInheritedWidget(_api!, child: const MyHomePage()),
);
}
class MyInheritedWidget extends InheritedWidget {
final FakeApi api;
const MyInheritedWidget(this.api, {required Widget child})
: super(child: child, key: const Key('MyInheritedWidget'));
static MyInheritedWidget? of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
@override
bool updateShouldNotify(MyInheritedWidget old) => false;
}
class MyHomePage extends StatelessWidget {
const MyHomePage() : super(key: const Key('MyHomePage'));
@override
Widget build(BuildContext context) => Builder(
builder: (context) => Scaffold(
backgroundColor: Colors.blueGrey,
body: StreamBuilder<List<ItemWidget>>(
stream: MyInheritedWidget.of(context)!.api.stream,
initialData: const [],
builder: (context, list) => list.hasError
? const Center(child: Icon(Icons.error))
: !list.hasData
? const Center(child: CircularProgressIndicator())
: list.data!.isEmpty
? const Center(
child: Text(
'the list is empty',
textScaleFactor: 1.5,
))
: ListView.builder(
itemCount: list.data!.length,
itemBuilder: (context, index) {
print('the list is building');
return list.data![index];
},
),
),
floatingActionButton: FloatingActionButton(
backgroundColor: Colors.white,
child: const Icon(Icons.add, color: Colors.blueGrey),
onPressed: MyInheritedWidget.of(context)!.api.add,
),
),
);
}
class ItemWidget extends StatelessWidget {
ItemWidget(this.text) : super(key: UniqueKey());
final String text;
@override
Widget build(BuildContext context) {
print('Item $text is building');
return Center(
child: Container(
padding: const EdgeInsets.only(bottom: 20),
width: MediaQuery.of(context).size.width * .5,
child: Card(
elevation: 10,
child: ListTile(
leading: GestureDetector(
child: const Icon(Icons.edit),
onTap: () => MyInheritedWidget.of(context)!.api.edit(key!),
),
trailing: GestureDetector(
child: const Icon(Icons.delete),
onTap: () => MyInheritedWidget.of(context)!.api.delete(key!),
),
title: Text(text),
),
),
),
);
}
}
class ItemDialog extends StatefulWidget {
const ItemDialog(this.text);
final String text;
@override
_ItemDialogState createState() => _ItemDialogState();
}
class _ItemDialogState extends State<ItemDialog> {
TextEditingController? _controller;
@override
void initState() {
_controller = TextEditingController()..text = widget.text;
super.initState();
}
@override
void dispose() {
_controller?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => AlertDialog(
content: Stack(
alignment: Alignment.center,
children: <Widget>[
SizedBox(
width: double.infinity,
height: MediaQuery.of(context).size.height * .3,
child: Center(
child: TextField(
autofocus: true,
controller: _controller,
),
),
),
],
),
actions: <Widget>[
IconButton(
onPressed: () => Navigator.pop(context, _controller!.text),
icon: const Icon(Icons.save),
),
],
);
}
class FakeApi {
FakeApi(this.navigatorKey);
final GlobalKey<NavigatorState> navigatorKey;
final _list = <ItemWidget>[];
StreamController<List<ItemWidget>> _controller = StreamController();
StreamController<List<ItemWidget>> get _c =>
_controller = StreamController<List<ItemWidget>>.broadcast();
Stream<List<ItemWidget>> get stream => _c.stream;
void dispose() => _controller.close();
void delete(Key key) {
_list.removeWhere((ItemWidget item) => item.key == key);
_c.sink.add(_list);
}
void edit(Key key) async {
final _item = _list.firstWhere((ItemWidget item) => item.key == key);
final _index = _list.lastIndexOf(_item);
final _text = await showDialog<String>(
context: navigatorKey.currentState!.overlay!.context,
builder: (context) => ItemDialog(
_item.text,
),
);
_list.removeAt(_index);
_list.insert(_index, ItemWidget(_text!));
_c.sink.add(_list);
}
void add() async {
final _text = await showDialog<String>(
context: navigatorKey.currentState!.overlay!.context,
builder: (context) => const ItemDialog(''),
);
_list.add(ItemWidget(_text!));
_c.sink.add(_list);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment