mirror of https://github.com/AMT-Cheif/drift.git
Update example: Can now update items
This commit is contained in:
parent
062deccb12
commit
d818942c8d
|
@ -265,6 +265,8 @@ Please note that a workaround for most on this list exists with custom statement
|
|||
These aren't sorted by priority. If you have more ideas or want some features happening soon,
|
||||
let us know by creating an issue!
|
||||
- Specify primary keys
|
||||
- Support an simplified update that doesn't need an explicit where based on the primary key
|
||||
- Data classes: Generate a `copyWith` method.
|
||||
- Simple `COUNT(*)` operations (group operations will be much more complicated)
|
||||
- Support default values and expressions
|
||||
- Allow using DAOs or some other mechanism instead of having to put everything in the main
|
||||
|
|
|
@ -48,8 +48,10 @@ class EditAction {
|
|||
/// number of addition and removal operations between the two lists. It has
|
||||
/// O(N + D^2) time performance, where D is the minimum amount of edits needed
|
||||
/// to turn a into b.
|
||||
List<EditAction> diff<T>(List<T> a, List<T> b) {
|
||||
final snakes = impl.calculateDiff(impl.DiffInput(a, b));
|
||||
List<EditAction> diff<T>(List<T> a, List<T> b,
|
||||
{bool Function(T a, T b) equals}) {
|
||||
final defaultEquals = equals ?? (T a, T b) => a == b;
|
||||
final snakes = impl.calculateDiff(impl.DiffInput<T>(a, b, defaultEquals));
|
||||
final actions = <EditAction>[];
|
||||
|
||||
var posOld = a.length;
|
||||
|
|
|
@ -11,9 +11,8 @@ import 'package:sally/src/runtime/statements/update.dart';
|
|||
/// This comes in handy to structure large amounts of database code better: The
|
||||
/// migration logic can live in the main [GeneratedDatabase] class, but code
|
||||
/// can be extracted into [DatabaseAccessor]s outside of that database.
|
||||
abstract class DatabaseAccessor<T extends GeneratedDatabase> extends DatabaseConnectionUser
|
||||
with QueryEngine {
|
||||
|
||||
abstract class DatabaseAccessor<T extends GeneratedDatabase>
|
||||
extends DatabaseConnectionUser with QueryEngine {
|
||||
@protected
|
||||
final T db;
|
||||
|
||||
|
@ -154,7 +153,9 @@ abstract class GeneratedDatabase extends DatabaseConnectionUser
|
|||
|
||||
GeneratedDatabase(SqlTypeSystem types, QueryExecutor executor,
|
||||
{StreamQueryStore streamStore})
|
||||
: super(types, executor, streamQueries: streamStore);
|
||||
: super(types, executor, streamQueries: streamStore) {
|
||||
executor.databaseInfo = this;
|
||||
}
|
||||
|
||||
/// Creates a migrator with the provided query executor. We sometimes can't
|
||||
/// use the regular [GeneratedDatabase.executor] because migration happens
|
||||
|
|
|
@ -7,23 +7,18 @@ class UpdateStatement<T, D> extends Query<T, D> {
|
|||
UpdateStatement(QueryEngine database, TableInfo<T, D> table)
|
||||
: super(database, table);
|
||||
|
||||
/// The object to update. The non-null fields of this object will be written
|
||||
/// into the rows matched by [whereExpr] and [limitExpr].
|
||||
D _updateReference;
|
||||
Map<String, dynamic> _updatedFields;
|
||||
|
||||
@override
|
||||
void writeStartPart(GenerationContext ctx) {
|
||||
// TODO support the OR (ROLLBACK / ABORT / REPLACE / FAIL / IGNORE...) thing
|
||||
|
||||
final map = table.entityToSql(_updateReference)
|
||||
..remove((_, value) => value == null);
|
||||
|
||||
ctx.buffer.write('UPDATE ${table.$tableName} SET ');
|
||||
|
||||
var first = true;
|
||||
map.forEach((columnName, variable) {
|
||||
_updatedFields.forEach((columnName, variable) {
|
||||
if (!first) {
|
||||
ctx.writeWhitespace();
|
||||
ctx.buffer.write(', ');
|
||||
} else {
|
||||
first = false;
|
||||
}
|
||||
|
@ -39,12 +34,19 @@ class UpdateStatement<T, D> extends Query<T, D> {
|
|||
/// means that, when you're not setting a where or limit expression
|
||||
/// explicitly, this method will update all rows in the specific table.
|
||||
Future<int> write(D entity) async {
|
||||
_updateReference = entity;
|
||||
if (!table.validateIntegrity(_updateReference, false)) {
|
||||
if (!table.validateIntegrity(entity, false)) {
|
||||
throw InvalidDataException(
|
||||
'Invalid data: $entity cannot be written into ${table.$tableName}');
|
||||
}
|
||||
|
||||
_updatedFields = table.entityToSql(entity)
|
||||
..remove((_, value) => value == null);
|
||||
|
||||
if (_updatedFields.isEmpty) {
|
||||
// nothing to update, we're done
|
||||
return Future.value(0);
|
||||
}
|
||||
|
||||
final ctx = constructQuery();
|
||||
final rows = await ctx.database.executor.doWhenOpened((e) async {
|
||||
return await e.runUpdate(ctx.sql, ctx.boundVariables);
|
||||
|
|
|
@ -38,10 +38,13 @@ class Range {
|
|||
class DiffInput<T> {
|
||||
final List<T> from;
|
||||
final List<T> to;
|
||||
final bool Function(T a, T b) equals;
|
||||
|
||||
DiffInput(this.from, this.to);
|
||||
DiffInput(this.from, this.to, this.equals);
|
||||
|
||||
bool areItemsTheSame(int fromPos, int toPos) => from[fromPos] == to[toPos];
|
||||
bool areItemsTheSame(int fromPos, int toPos) {
|
||||
return equals(from[fromPos], to[toPos]);
|
||||
}
|
||||
}
|
||||
|
||||
List<Snake> calculateDiff(DiffInput input) {
|
||||
|
|
|
@ -265,6 +265,8 @@ Please note that a workaround for most on this list exists with custom statement
|
|||
These aren't sorted by priority. If you have more ideas or want some features happening soon,
|
||||
let us know by creating an issue!
|
||||
- Specify primary keys
|
||||
- Support an simplified update that doesn't need an explicit where based on the primary key
|
||||
- Data classes: Generate a `copyWith` method.
|
||||
- Simple `COUNT(*)` operations (group operations will be much more complicated)
|
||||
- Support default values and expressions
|
||||
- Allow using DAOs or some other mechanism instead of having to put everything in the main
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:sally_example/database/database.dart';
|
||||
|
||||
class TodoAppBloc {
|
||||
final Database db;
|
||||
Stream<List<TodoEntry>> get allEntries => db.allEntries();
|
||||
|
||||
TodoAppBloc() : db = Database();
|
||||
|
||||
void addEntry(TodoEntry entry) {
|
||||
db.addEntry(entry);
|
||||
}
|
||||
}
|
|
@ -8,8 +8,6 @@ part 'database.g.dart';
|
|||
class Todos extends Table {
|
||||
IntColumn get id => integer().autoIncrement()();
|
||||
|
||||
TextColumn get title => text().withLength(min: 4, max: 16).nullable()();
|
||||
|
||||
TextColumn get content => text()();
|
||||
|
||||
DateTimeColumn get targetDate => dateTime().nullable()();
|
||||
|
@ -53,11 +51,13 @@ class Database extends _$Database {
|
|||
// select all categories and load how many associated entries there are for
|
||||
// each category
|
||||
return customSelectStream(
|
||||
'SELECT *, (SELECT COUNT(*) FROM todos WHERE category = c.id) AS "amount" FROM categories c;',
|
||||
readsFrom: {todos, categories}).map((rows) {
|
||||
'SELECT *, (SELECT COUNT(*) FROM todos WHERE category = c.id) AS "amount" FROM categories c;',
|
||||
readsFrom: {todos, categories})
|
||||
.map((rows) {
|
||||
// when we have the result set, map each row to the data class
|
||||
return rows
|
||||
.map((row) => CategoryWithCount(Category.fromData(row.data, this), row.readInt('amount')))
|
||||
.map((row) => CategoryWithCount(
|
||||
Category.fromData(row.data, this), row.readInt('amount')))
|
||||
.toList();
|
||||
});
|
||||
}
|
||||
|
@ -73,4 +73,14 @@ class Database extends _$Database {
|
|||
Future deleteEntry(TodoEntry entry) {
|
||||
return (delete(todos)..where((t) => t.id.equals(entry.id))).go();
|
||||
}
|
||||
|
||||
Future updateContent(int id, String content) {
|
||||
return (update(todos)..where((t) => t.id.equals(id)))
|
||||
.write(TodoEntry(content: content));
|
||||
}
|
||||
|
||||
Future updateDate(int id, DateTime dueDate) {
|
||||
return (update(todos)..where((t) => t.id.equals(id)))
|
||||
.write(TodoEntry(targetDate: dueDate));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,19 +8,16 @@ part of 'database.dart';
|
|||
|
||||
class TodoEntry {
|
||||
final int id;
|
||||
final String title;
|
||||
final String content;
|
||||
final DateTime targetDate;
|
||||
final int category;
|
||||
TodoEntry(
|
||||
{this.id, this.title, this.content, this.targetDate, this.category});
|
||||
TodoEntry({this.id, this.content, this.targetDate, this.category});
|
||||
factory TodoEntry.fromData(Map<String, dynamic> data, GeneratedDatabase db) {
|
||||
final intType = db.typeSystem.forDartType<int>();
|
||||
final stringType = db.typeSystem.forDartType<String>();
|
||||
final dateTimeType = db.typeSystem.forDartType<DateTime>();
|
||||
return TodoEntry(
|
||||
id: intType.mapFromDatabaseResponse(data['id']),
|
||||
title: stringType.mapFromDatabaseResponse(data['title']),
|
||||
content: stringType.mapFromDatabaseResponse(data['content']),
|
||||
targetDate: dateTimeType.mapFromDatabaseResponse(data['target_date']),
|
||||
category: intType.mapFromDatabaseResponse(data['category']),
|
||||
|
@ -28,8 +25,7 @@ class TodoEntry {
|
|||
}
|
||||
@override
|
||||
int get hashCode =>
|
||||
((((id.hashCode) * 31 + title.hashCode) * 31 + content.hashCode) * 31 +
|
||||
targetDate.hashCode) *
|
||||
(((id.hashCode) * 31 + content.hashCode) * 31 + targetDate.hashCode) *
|
||||
31 +
|
||||
category.hashCode;
|
||||
@override
|
||||
|
@ -37,7 +33,6 @@ class TodoEntry {
|
|||
identical(this, other) ||
|
||||
(other is TodoEntry &&
|
||||
other.id == id &&
|
||||
other.title == title &&
|
||||
other.content == content &&
|
||||
other.targetDate == targetDate &&
|
||||
other.category == category);
|
||||
|
@ -50,11 +45,6 @@ class $TodosTable extends Todos implements TableInfo<Todos, TodoEntry> {
|
|||
GeneratedIntColumn get id =>
|
||||
GeneratedIntColumn('id', false, hasAutoIncrement: true);
|
||||
@override
|
||||
GeneratedTextColumn get title => GeneratedTextColumn(
|
||||
'title',
|
||||
true,
|
||||
);
|
||||
@override
|
||||
GeneratedTextColumn get content => GeneratedTextColumn(
|
||||
'content',
|
||||
false,
|
||||
|
@ -70,8 +60,7 @@ class $TodosTable extends Todos implements TableInfo<Todos, TodoEntry> {
|
|||
true,
|
||||
);
|
||||
@override
|
||||
List<GeneratedColumn> get $columns =>
|
||||
[id, title, content, targetDate, category];
|
||||
List<GeneratedColumn> get $columns => [id, content, targetDate, category];
|
||||
@override
|
||||
Todos get asDslTable => this;
|
||||
@override
|
||||
|
@ -79,7 +68,6 @@ class $TodosTable extends Todos implements TableInfo<Todos, TodoEntry> {
|
|||
@override
|
||||
bool validateIntegrity(TodoEntry instance, bool isInserting) =>
|
||||
id.isAcceptableValue(instance.id, isInserting) &&
|
||||
title.isAcceptableValue(instance.title, isInserting) &&
|
||||
content.isAcceptableValue(instance.content, isInserting) &&
|
||||
targetDate.isAcceptableValue(instance.targetDate, isInserting) &&
|
||||
category.isAcceptableValue(instance.category, isInserting);
|
||||
|
@ -96,9 +84,6 @@ class $TodosTable extends Todos implements TableInfo<Todos, TodoEntry> {
|
|||
if (d.id != null) {
|
||||
map['id'] = Variable<int, IntType>(d.id);
|
||||
}
|
||||
if (d.title != null) {
|
||||
map['title'] = Variable<String, StringType>(d.title);
|
||||
}
|
||||
if (d.content != null) {
|
||||
map['content'] = Variable<String, StringType>(d.content);
|
||||
}
|
||||
|
|
|
@ -9,8 +9,12 @@ part 'todos_dao.g.dart';
|
|||
class TodosDao extends DatabaseAccessor<Database> with _TodosDaoMixin {
|
||||
TodosDao(Database db) : super(db);
|
||||
|
||||
Stream<List<TodoEntry>> todosWithoutCategory() {
|
||||
return null;
|
||||
Stream<List<TodoEntry>> todosInCategory(Category category) {
|
||||
if (category == null) {
|
||||
return (select(todos)..where((t) => isNull(t.category))).watch();
|
||||
} else {
|
||||
return (select(todos)..where((t) => t.category.equals(category.id)))
|
||||
.watch();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:sally_example/database/database.dart';
|
||||
import 'package:sally_example/bloc.dart';
|
||||
import 'widgets/homescreen.dart';
|
||||
|
||||
void main() => runApp(MyApp());
|
||||
|
@ -14,18 +14,18 @@ class MyApp extends StatefulWidget {
|
|||
// We use this widget to set up the material app and provide an InheritedWidget that
|
||||
// the rest of this simple app can then use to access the database
|
||||
class MyAppState extends State<MyApp> {
|
||||
Database _db;
|
||||
TodoAppBloc bloc;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_db = Database();
|
||||
bloc = TodoAppBloc();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DatabaseProvider(
|
||||
db: _db,
|
||||
return BlocProvider(
|
||||
bloc: bloc,
|
||||
child: MaterialApp(
|
||||
title: 'Sally Demo',
|
||||
theme: ThemeData(
|
||||
|
@ -37,17 +37,16 @@ class MyAppState extends State<MyApp> {
|
|||
}
|
||||
}
|
||||
|
||||
class DatabaseProvider extends InheritedWidget {
|
||||
final Database db;
|
||||
class BlocProvider extends InheritedWidget {
|
||||
final TodoAppBloc bloc;
|
||||
|
||||
DatabaseProvider({@required this.db, Widget child}) : super(child: child);
|
||||
BlocProvider({@required this.bloc, Widget child}) : super(child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(DatabaseProvider oldWidget) {
|
||||
return oldWidget.db != db;
|
||||
bool updateShouldNotify(BlocProvider oldWidget) {
|
||||
return oldWidget.bloc != bloc;
|
||||
}
|
||||
|
||||
static Database provideDb(BuildContext ctx) =>
|
||||
(ctx.inheritFromWidgetOfExactType(DatabaseProvider) as DatabaseProvider)
|
||||
.db;
|
||||
static TodoAppBloc provideBloc(BuildContext ctx) =>
|
||||
(ctx.inheritFromWidgetOfExactType(BlocProvider) as BlocProvider).bloc;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:sally_example/bloc.dart';
|
||||
import 'package:sally_example/database/database.dart';
|
||||
import 'package:sally_example/main.dart';
|
||||
import 'package:sally_example/widgets/todo_card.dart';
|
||||
|
@ -13,12 +14,12 @@ class HomeScreen extends StatefulWidget {
|
|||
}
|
||||
}
|
||||
|
||||
/// Shows a list of todo entries and displays a text input to add another one
|
||||
/// Shows a list of todos and displays a text input to add another one
|
||||
class HomeScreenState extends State<HomeScreen> {
|
||||
// we only use this to reset the input field at the bottom when a entry has been added
|
||||
// we only use this to reset the input field at the bottom when a entry has been added
|
||||
final TextEditingController controller = TextEditingController();
|
||||
|
||||
Database get db => DatabaseProvider.provideDb(context);
|
||||
TodoAppBloc get bloc => BlocProvider.provideBloc(context);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -29,9 +30,14 @@ class HomeScreenState extends State<HomeScreen> {
|
|||
// A SallyAnimatedList automatically animates incoming and leaving items, we only
|
||||
// have to tell it what data to display and how to turn data into widgets.
|
||||
body: SallyAnimatedList<TodoEntry>(
|
||||
stream: db.allEntries(), // we want to show an updating stream of all todo entries
|
||||
stream: bloc
|
||||
.allEntries, // we want to show an updating stream of all entries
|
||||
// consider items equal if their id matches. Otherwise, we'd get an
|
||||
// animation of an old item leaving and another one coming in every time
|
||||
// the content of an item changed!
|
||||
equals: (a, b) => a.id == b.id,
|
||||
itemBuilder: (ctx, item, animation) {
|
||||
// When a new item arrives, it will expand verticallly
|
||||
// When a new item arrives, it will expand vertically
|
||||
return SizeTransition(
|
||||
key: ObjectKey(item.id),
|
||||
sizeFactor: animation,
|
||||
|
@ -46,15 +52,15 @@ class HomeScreenState extends State<HomeScreen> {
|
|||
sizeFactor: animation,
|
||||
axis: Axis.vertical,
|
||||
child: AnimatedBuilder(
|
||||
animation: CurvedAnimation(parent: animation, curve: Curves.easeOut),
|
||||
child: TodoCard(item),
|
||||
builder: (context, child) {
|
||||
return Opacity(
|
||||
opacity: animation.value,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
),
|
||||
animation:
|
||||
CurvedAnimation(parent: animation, curve: Curves.easeOut),
|
||||
child: TodoCard(item),
|
||||
builder: (context, child) {
|
||||
return Opacity(
|
||||
opacity: animation.value,
|
||||
child: child,
|
||||
);
|
||||
}),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
@ -95,7 +101,7 @@ class HomeScreenState extends State<HomeScreen> {
|
|||
if (controller.text.isNotEmpty) {
|
||||
// We write the entry here. Notice how we don't have to call setState()
|
||||
// or anything - sally will take care of updating the list automatically.
|
||||
db.addEntry(TodoEntry(content: controller.text));
|
||||
bloc.addEntry(TodoEntry(content: controller.text));
|
||||
controller.clear();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:sally_example/database/database.dart';
|
||||
import 'package:sally_example/main.dart';
|
||||
import 'package:sally_example/widgets/todo_edit_dialog.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
|
||||
/// Card that displays a todo entry and an icon button to delete that entry
|
||||
final DateFormat _format = DateFormat.yMMMd();
|
||||
|
||||
/// Card that displays an entry and an icon button to delete that entry
|
||||
class TodoCard extends StatelessWidget {
|
||||
final TodoEntry entry;
|
||||
|
||||
|
@ -10,20 +14,70 @@ class TodoCard extends StatelessWidget {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget dueDate;
|
||||
if (entry.targetDate == null) {
|
||||
dueDate = const Text(
|
||||
'No due date set',
|
||||
style: TextStyle(color: Colors.grey, fontSize: 12),
|
||||
);
|
||||
} else {
|
||||
dueDate = Text(
|
||||
_format.format(entry.targetDate),
|
||||
style: const TextStyle(fontSize: 12),
|
||||
);
|
||||
}
|
||||
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: <Widget>[
|
||||
Expanded(child: Text(entry.content)),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(entry.content),
|
||||
dueDate,
|
||||
],
|
||||
),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.calendar_today),
|
||||
color: Colors.green,
|
||||
onPressed: () async {
|
||||
final dateTime = await showDatePicker(
|
||||
context: context,
|
||||
initialDate: DateTime.now(),
|
||||
firstDate: DateTime(2019),
|
||||
lastDate: DateTime(3038));
|
||||
|
||||
await BlocProvider.provideBloc(context)
|
||||
.db
|
||||
.updateDate(entry.id, dateTime);
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.edit),
|
||||
color: Colors.blue,
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (ctx) => TodoEditDialog(
|
||||
entry: entry,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
color: Colors.red,
|
||||
onPressed: () {
|
||||
// We delete the entry here. Again, notice how we don't have to call setState() or
|
||||
// inform the parent widget. The animated list will take care of this automatically.
|
||||
DatabaseProvider.provideDb(context).deleteEntry(entry);
|
||||
BlocProvider.provideBloc(context).db.deleteEntry(entry);
|
||||
},
|
||||
)
|
||||
],
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:sally_example/database/database.dart';
|
||||
import 'package:sally_example/main.dart';
|
||||
|
||||
class TodoEditDialog extends StatefulWidget {
|
||||
final TodoEntry entry;
|
||||
|
||||
const TodoEditDialog({Key key, this.entry}) : super(key: key);
|
||||
|
||||
@override
|
||||
_TodoEditDialogState createState() => _TodoEditDialogState();
|
||||
}
|
||||
|
||||
class _TodoEditDialogState extends State<TodoEditDialog> {
|
||||
final TextEditingController textController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
textController.text = widget.entry.content;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
textController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Edit entry'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextField(
|
||||
controller: textController,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'What needs to be done?',
|
||||
helperText: 'Content of entry',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
FlatButton(
|
||||
child: const Text('Cancel'),
|
||||
textColor: Colors.red,
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
FlatButton(
|
||||
child: const Text('Save'),
|
||||
onPressed: () {
|
||||
final entry = widget.entry;
|
||||
if (textController.text.isNotEmpty) {
|
||||
BlocProvider.provideBloc(context)
|
||||
.db
|
||||
.updateContent(entry.id, textController.text);
|
||||
}
|
||||
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ environment:
|
|||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
intl:
|
||||
cupertino_icons: ^0.1.2
|
||||
rxdart: 0.20.0
|
||||
sally_flutter:
|
||||
|
|
|
@ -14,10 +14,17 @@ class SallyAnimatedList<T> extends StatefulWidget {
|
|||
final ItemBuilder<T> itemBuilder;
|
||||
final RemovedItemBuilder<T> removedItemBuilder;
|
||||
|
||||
/// A function that decides whether two items are considered equal. By
|
||||
/// default, `a == b` will be used. A customization is useful if the content
|
||||
/// of items can change (e.g. when a title changes, you'd only want to change
|
||||
/// one text and not let the item disappear to show up again).
|
||||
final bool Function(T a, T b) equals;
|
||||
|
||||
SallyAnimatedList(
|
||||
{@required this.stream,
|
||||
@required this.itemBuilder,
|
||||
@required this.removedItemBuilder});
|
||||
@required this.removedItemBuilder,
|
||||
this.equals});
|
||||
|
||||
@override
|
||||
_SallyAnimatedListState<T> createState() {
|
||||
|
@ -56,7 +63,7 @@ class _SallyAnimatedListState<T> extends State<SallyAnimatedList<T>> {
|
|||
listState.insertItem(i);
|
||||
}
|
||||
} else {
|
||||
final editScript = diff(_lastSnapshot, data);
|
||||
final editScript = diff(_lastSnapshot, data, equals: widget.equals);
|
||||
|
||||
for (var action in editScript) {
|
||||
if (action.isDelete) {
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
void insertIntoSortedList<T>(List<T> list, T entry, {int compare(T a, T b)}) {}
|
|
@ -45,7 +45,8 @@ class DaoGenerator extends GeneratorForAnnotation<UseDao> {
|
|||
'DatabaseAccessor<${dbImpl.displayName}> {\n');
|
||||
|
||||
for (var table in tableTypes) {
|
||||
final infoType = tableInfoNameForTableClass(table.element as ClassElement);
|
||||
final infoType =
|
||||
tableInfoNameForTableClass(table.element as ClassElement);
|
||||
final getterName = ReCase(table.name).camelCase;
|
||||
|
||||
buffer.write('$infoType get $getterName => db.$getterName;\n');
|
||||
|
|
|
@ -18,4 +18,5 @@ class SpecifiedTable {
|
|||
{this.fromClass, this.columns, this.sqlName, this.dartTypeName});
|
||||
}
|
||||
|
||||
String tableInfoNameForTableClass(ClassElement fromClass) => '\$${fromClass.name}Table';
|
||||
String tableInfoNameForTableClass(ClassElement fromClass) =>
|
||||
'\$${fromClass.name}Table';
|
||||
|
|
Loading…
Reference in New Issue