Skip to content

Instantly share code, notes, and snippets.

@slightfoot
Created March 13, 2019 13:08
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save slightfoot/7a4cf14931baf28aa5beb4bb2f8a29b7 to your computer and use it in GitHub Desktop.
Save slightfoot/7a4cf14931baf28aa5beb4bb2f8a29b7 to your computer and use it in GitHub Desktop.
Basic Reorderable list from Firestore Documents using only inbuilt Widgets. 13th March 2019
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: Colors.indigo,
accentColor: Colors.pinkAccent,
),
home: ExampleScreen(),
),
);
}
class ExampleScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Material(
child: SafeArea(
child: ReorderableFirebaseList(
collection: Firestore.instance.collection('items'),
indexKey: 'pos',
itemBuilder: (BuildContext context, int index, DocumentSnapshot doc) {
return ListTile(
key: Key(doc.documentID),
title: Text(doc.data['title']),
);
},
),
),
);
}
}
typedef ReorderableWidgetBuilder = Widget Function(BuildContext context, int index, DocumentSnapshot doc);
class ReorderableFirebaseList extends StatefulWidget {
const ReorderableFirebaseList({
Key key,
@required this.collection,
@required this.indexKey,
@required this.itemBuilder,
this.descending = false,
}) : super(key: key);
final CollectionReference collection;
final String indexKey;
final bool descending;
final ReorderableWidgetBuilder itemBuilder;
@override
_ReorderableFirebaseListState createState() => _ReorderableFirebaseListState();
}
class _ReorderableFirebaseListState extends State<ReorderableFirebaseList> {
List<DocumentSnapshot> _docs;
Future _saving;
@override
Widget build(BuildContext context) {
return FutureBuilder(
future: _saving,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.none || snapshot.connectionState == ConnectionState.done) {
return StreamBuilder<QuerySnapshot>(
stream: widget.collection.orderBy(widget.indexKey, descending: widget.descending).snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasData) {
_docs = snapshot.data.documents;
return ReorderableListView(
onReorder: _onReorder,
children: List.generate(_docs.length, (int index) {
return widget.itemBuilder(context, index, _docs[index]);
}),
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
);
}
void _onReorder(int oldIndex, int newIndex) {
if (oldIndex < newIndex) newIndex -= 1;
_docs.insert(newIndex, _docs.removeAt(oldIndex));
final futures = <Future>[];
for (int pos = 0; pos < _docs.length; pos++) {
futures.add(_docs[pos].reference.updateData({widget.indexKey: pos}));
}
setState(() {
_saving = Future.wait(futures);
});
}
}
@happyharis
Copy link

Thanks for this gist. I was thinking, to avoid the reloading after re-arranging the tiles, we can batch the updates rather than awaiting for it. It would look something like this:

void _onReorder(int oldIndex, int newIndex) {
  if (oldIndex < newIndex) newIndex -= 1;
  _docs.insert(newIndex, _docs.removeAt(oldIndex));
  final batch = Firestore.instance.batch();
  for (int pos = 0; pos < _docs.length; pos++) {
    batch.updateData(_docs[pos].reference, {widget.indexKey: pos});
  }
  batch.commit();
}

I understand the need to having the futures to complete to avoid any mismatch of the local docs with firestore docs. What do you think?

@tinypell3ts
Copy link

tinypell3ts commented Jul 3, 2020

This was super useful, thanks guys 👍

@welfvh
Copy link

welfvh commented Oct 27, 2020

Does this require giving the docs special ids to make them work as index keys or is autoID sufficient?

@aminekha
Copy link

aminekha commented Nov 6, 2020

I need to use the ".where" to filter data. However, when I change CollectionReference to Query it does not reorder anymore. Is there a solution?

@GroovinChip
Copy link

Would just like to add that with null safety and @HappyHarris's batched _onReorder() function, the FutureBuilder above the StreamBuilder becomes deprecated.

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';

typedef ReorderableWidgetBuilder = Widget Function(
    BuildContext context, int index, DocumentSnapshot doc);

class ReorderableFirebaseList extends StatefulWidget {
  const ReorderableFirebaseList({
    Key? key,
    required this.collection,
    required this.indexKey,
    required this.itemBuilder,
    this.descending = false,
    this.emptyWidget,
  }) : super(key: key);

  final CollectionReference collection;
  final String indexKey;
  final bool descending;
  final ReorderableWidgetBuilder itemBuilder;
  final Widget? emptyWidget;

  @override
  _ReorderableFirebaseListState createState() =>
      _ReorderableFirebaseListState();
}

class _ReorderableFirebaseListState extends State<ReorderableFirebaseList> {
  late List<DocumentSnapshot> _docs;

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<QuerySnapshot>(
      stream: widget.collection
          .orderBy(widget.indexKey, descending: widget.descending)
          .snapshots(),
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          _docs = snapshot.data!.docs;
          if (_docs.isNotEmpty) {
            return ReorderableListView(
              onReorder: _onReorder,
              children: List.generate(_docs.length, (int index) {
                return widget.itemBuilder(context, index, _docs[index]);
              }),
            );
          } else {
            return widget.emptyWidget ?? Container();
          }
        } else {
          return const Center(
            child: CircularProgressIndicator(),
          );
        }
      },
    );
  }

  void _onReorder(int oldIndex, int newIndex) {
    if (oldIndex < newIndex) newIndex -= 1;
    _docs.insert(newIndex, _docs.removeAt(oldIndex));
    final batch = FirebaseFirestore.instance.batch();
    for (int pos = 0; pos < _docs.length; pos++) {
      batch.update(_docs[pos].reference, {widget.indexKey: pos});
    }
    batch.commit();
  }
}

@murtaza-bhaisaheb
Copy link

Does this require giving the docs special ids to make them work as index keys or is autoID sufficient?

I have same doubt so can you please help if you have solved?

@RichardPavlikan
Copy link

Guys thanks a lot for this code, really helpful.

@myagizguler
Copy link

Thanks a lot

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment