Skip to content

Commit

Permalink
add example
Browse files Browse the repository at this point in the history
  • Loading branch information
dikatok committed Sep 3, 2024
1 parent d44c2bc commit 5fe3b01
Show file tree
Hide file tree
Showing 21 changed files with 552 additions and 256 deletions.
2 changes: 2 additions & 0 deletions example/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
TURSO_URL=
TURSO_TOKEN=
15 changes: 6 additions & 9 deletions example/README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
# libsql_dart_example

Demonstrates how to use the libsql_dart plugin.

## Getting Started

This project is a starting point for a Flutter application.
Do either of the following:

A few resources to get you started if this is your first Flutter project:
- Create a remote Turso database
- Run a local Turso database `https://docs.turso.tech/local-development#turso-cli`
- Run a local libsql instance using the provided `docker-compose.yaml`

- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
Fill the credentials in `.env`

For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
Run the example `flutter run --debug --dart-define-from-file=.env`
7 changes: 7 additions & 0 deletions example/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
services:
libsql:
container_name: libsql_dart_sqld
image: ghcr.io/tursodatabase/libsql-server:v0.24.23
restart: always
ports:
- 8081:8080
7 changes: 0 additions & 7 deletions example/integration_test/simple_test.dart

This file was deleted.

8 changes: 8 additions & 0 deletions example/lib/bootstrap.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import 'package:libsql_dart/libsql_dart.dart';

Future<void> bootstrapDatabase(LibsqlClient client) async {
await client.connect();
// await client.execute("drop table if exists tasks");
await client.execute(
"create table if not exists tasks (id integer primary key, title text, description text, completed integer)");
}
2 changes: 2 additions & 0 deletions example/lib/features/task/blocs/blocs.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export 'task_list_cubit.dart';
export 'task_list_state.dart';
49 changes: 49 additions & 0 deletions example/lib/features/task/blocs/task_list_cubit.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:libsql_dart_example/features/task/blocs/task_list_state.dart';
import 'package:libsql_dart_example/features/task/models/models.dart';
import 'package:libsql_dart_example/features/task/repositories/repositories.dart';

class TaskListCubit extends Cubit<TaskListState> {
final TaskRepository _taskRepository;

TaskListCubit(this._taskRepository) : super(TaskListInitial()) {
getTasks();
}

Future<void> getTasks() async {
emit(TaskListLoading());
try {
final tasks = await _taskRepository.getTasks();
emit(TaskListLoaded(tasks));
} catch (e) {
emit(TaskListError(e.toString()));
}
}

Future<void> addTask(Task task) async {
try {
await _taskRepository.addTask(task);
await getTasks();
} catch (e) {
emit(TaskListError(e.toString()));
}
}

Future<void> deleteTask(int id) async {
try {
await _taskRepository.deleteTask(id);
await getTasks();
} catch (e) {
emit(TaskListError(e.toString()));
}
}

Future<void> markTasksAsCompleted(List<int> ids) async {
try {
await _taskRepository.markTasksAsCompleted(ids);
await getTasks();
} catch (e) {
emit(TaskListError(e.toString()));
}
}
}
17 changes: 17 additions & 0 deletions example/lib/features/task/blocs/task_list_state.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import 'package:libsql_dart_example/features/task/models/models.dart';

abstract class TaskListState {}

class TaskListInitial extends TaskListState {}

class TaskListLoading extends TaskListState {}

class TaskListLoaded extends TaskListState {
final List<Task> tasks;
TaskListLoaded(this.tasks);
}

class TaskListError extends TaskListState {
final String message;
TaskListError(this.message);
}
1 change: 1 addition & 0 deletions example/lib/features/task/models/models.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export 'task.dart';
13 changes: 13 additions & 0 deletions example/lib/features/task/models/task.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class Task {
final int id;
final String title;
final String description;
final bool completed;

const Task({
required this.id,
required this.title,
required this.description,
required this.completed,
});
}
1 change: 1 addition & 0 deletions example/lib/features/task/repositories/repositories.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export 'task_repository.dart';
11 changes: 11 additions & 0 deletions example/lib/features/task/repositories/task_repository.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'package:libsql_dart_example/features/task/models/models.dart';

abstract class TaskRepository {
Future<List<Task>> getTasks();

Future<void> addTask(Task task);

Future<void> deleteTask(int id);

Future<void> markTasksAsCompleted(List<int> ids);
}
4 changes: 4 additions & 0 deletions example/lib/features/task/task.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export 'blocs/blocs.dart';
export 'models/models.dart';
export 'repositories/repositories.dart';
export 'task_list.dart';
87 changes: 87 additions & 0 deletions example/lib/features/task/task_add.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import 'package:flutter/material.dart';

class TaskAdd extends StatefulWidget {
const TaskAdd({super.key});

@override
State<TaskAdd> createState() => _TaskAddState();
}

class _TaskAddState extends State<TaskAdd> {
final formKey = GlobalKey<FormState>();
final titleController = TextEditingController();
final descriptionController = TextEditingController();

@override
Widget build(BuildContext context) {
return BottomSheet(
onClosing: () {},
enableDrag: false,
builder: (context) {
return SingleChildScrollView(
child: Form(
key: formKey,
autovalidateMode: AutovalidateMode.disabled,
child: Padding(
padding: const EdgeInsets.all(24).add(EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text('Add new task', style: TextStyle(fontSize: 24)),
const SizedBox(height: 16),
const Text('Name'),
TextFormField(
controller: titleController,
textCapitalization: TextCapitalization.sentences,
onTap: () {
titleController.selection = TextSelection(
baseOffset: 0,
extentOffset: titleController.text.length,
);
},
validator: (value) {
if (value?.isEmpty ?? true) {
return 'Must not be empty';
}
return null;
},
),
const SizedBox(height: 16),
const Text('Description'),
TextFormField(
controller: descriptionController,
textCapitalization: TextCapitalization.sentences,
onTap: () {
titleController.selection = TextSelection(
baseOffset: 0,
extentOffset: titleController.text.length,
);
},
validator: (value) {
if (value?.isEmpty ?? true) {
return 'Must not be empty';
}
return null;
},
),
const SizedBox(height: 32),
FilledButton(
onPressed: () {
if (!formKey.currentState!.validate()) return;
Navigator.maybePop(context, {
'title': titleController.text,
'description': descriptionController.text,
});
},
child: const Text('Add task'),
),
],
),
),
),
);
},
);
}
}
103 changes: 103 additions & 0 deletions example/lib/features/task/task_list.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:libsql_dart_example/features/task/blocs/blocs.dart';
import 'package:libsql_dart_example/features/task/models/task.dart';
import 'package:libsql_dart_example/features/task/repositories/repositories.dart';
import 'package:libsql_dart_example/features/task/task_add.dart';

class TaskList extends StatefulWidget {
const TaskList({super.key});

@override
State<TaskList> createState() => _TaskListState();
}

class _TaskListState extends State<TaskList> {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => TaskListCubit(context.read<TaskRepository>()),
child: SafeArea(
child: Scaffold(
floatingActionButton: Builder(builder: (context) {
return FloatingActionButton(
onPressed: () async {
// final res = await memoryClient.execute(
// "create table tasks (id integer primary key, title string, description string, completed integer);");
// print(await memoryClient.query("select * from tasks;"));
// await context.read<TaskListCubit>().getTasks();
final taskData =
await showModalBottomSheet<Map<String, dynamic>>(
context: context,
isScrollControlled: true,
builder: (_) => const TaskAdd(),
);
if (taskData == null || !context.mounted) return;
await context.read<TaskListCubit>().addTask(Task(
id: -1,
title: taskData["title"],
description: taskData["description"],
completed: false));
},
child: const Icon(Icons.add),
);
}),
body: Padding(
padding: const EdgeInsets.all(24),
child: BlocBuilder<TaskListCubit, TaskListState>(
builder: (context, state) {
return switch (state) {
TaskListInitial() => const SizedBox.shrink(),
TaskListLoading() => const CircularProgressIndicator(),
TaskListLoaded(tasks: final tasks) => ListView.builder(
itemCount: tasks.length,
itemBuilder: (context, index) {
return Dismissible(
background: Container(color: Colors.red),
key: ValueKey(tasks[index].id),
onDismissed: (_) {
context
.read<TaskListCubit>()
.deleteTask(tasks[index].id);
},
child: CheckboxListTile(
value: tasks[index].completed,
title: Text(tasks[index].title),
subtitle: Text(tasks[index].description),
onChanged: tasks[index].completed
? null
: (value) {
context
.read<TaskListCubit>()
.markTasksAsCompleted(
[tasks[index].id]);
},
),
);
},
),
TaskListError(message: final message) => Text(message),
_ => throw Exception("Invalid state"),
};
},
),
),
),
),
);
}
}

class _TaskAdd extends StatefulWidget {
const _TaskAdd();

@override
State<_TaskAdd> createState() => __TaskAddState();
}

class __TaskAddState extends State<_TaskAdd> {
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}
1 change: 1 addition & 0 deletions example/lib/infra/infra.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export 'libsql_task_repository.dart';
40 changes: 40 additions & 0 deletions example/lib/infra/libsql_task_repository.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import 'package:libsql_dart/libsql_dart.dart';
import 'package:libsql_dart_example/features/task/models/task.dart';
import 'package:libsql_dart_example/features/task/repositories/repositories.dart';

class LibsqlTaskRepository extends TaskRepository {
final LibsqlClient _client;

LibsqlTaskRepository(this._client);

@override
Future<void> addTask(Task task) async {
await _client.execute(
"insert into tasks (title, description, completed) values (?, ?, ?)",
positional: [task.title, task.description, task.completed ? 1 : 0]);
}

@override
Future<void> deleteTask(int id) async {
await _client.execute("delete from tasks where id = ?", positional: [id]);
}

@override
Future<List<Task>> getTasks() async {
return _client.query("select * from tasks").then((value) => value
.map((row) => Task(
id: row["id"],
title: row["title"],
description: row["description"],
completed: row["completed"] == 1,
))
.toList());
}

@override
Future<void> markTasksAsCompleted(List<int> ids) async {
await _client.execute(
"update tasks set completed = 1 where id in (${ids.join(",")})",
);
}
}
Loading

0 comments on commit 5fe3b01

Please sign in to comment.