-
Notifications
You must be signed in to change notification settings - Fork 315
add flutter tutorial to build an ideas tracker app using appwrite #2888
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
blueberry-adii
wants to merge
18
commits into
appwrite:main
Choose a base branch
from
blueberry-adii:docs-2882-add-flutter-tutorial-using-appwrite
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 1 commit
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
9e41847
Write tutorial to build an ideas tracker app with flutter
blueberry-adii 331a168
Update step 4 and set obscure text to true in password field
blueberry-adii e7bbbbb
add type and size fields in table column properties
blueberry-adii 8c90904
handle exceptions by wrapping login and register in try-catch block
blueberry-adii 9debd4f
replace print with debugPrint for consistency
blueberry-adii 923f628
add steps to create a database in the console
blueberry-adii 4cbfb29
increase varchar size to 200 as per other tutorials
blueberry-adii e7e123c
handle exceptions by Ideas provider using try-catch blocks
blueberry-adii 60309e3
fix ID names for database and table for consistency
blueberry-adii 07b5349
mounted check before using context after async gap
blueberry-adii 07654cc
mounted check before using context after async gap
blueberry-adii f1f57cd
fix double catch blocks by removing empty catch block
blueberry-adii c07eb4e
fix error handling and UI inconsistencies
blueberry-adii f72b517
change description to match disable remove button if user not owner
blueberry-adii f181e6c
move isOwner check inside Button function outside to disable button c…
blueberry-adii 0960f13
replace Styles.disabledButton by adding disabledBackgroundColor to St…
blueberry-adii f25924e
fix typo: idea.userId to userId
blueberry-adii 5f13426
specify to replace <REGION> in appwrite endpoint
blueberry-adii File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,28 @@ | ||
| --- | ||
| layout: tutorial | ||
| title: Coming soon | ||
| description: Learn to build an Flutter app with no backend code using an Appwrite backend. | ||
| framework: Flutter | ||
| back: /docs/tutorials | ||
| category: Mobile and native | ||
| title: Build an ideas tracker with Flutter | ||
| description: Learn to build a Flutter app with no backend code using an Appwrite backend. | ||
| step: 1 | ||
| draft: true | ||
| difficulty: beginner | ||
| back: /docs/tutorials | ||
| readtime: 15 | ||
| category: Mobile and native | ||
| framework: Flutter | ||
| --- | ||
|
|
||
| Improve the docs, add this guide. | ||
| **Idea tracker**: an app to track all the side project ideas that you'll start, but probably never finish. | ||
| In this tutorial, you will build Idea tracker with Appwrite and Flutter. | ||
|
|
||
| ## Concepts {% #concepts %} | ||
| This tutorial will introduce the following concepts: | ||
|
|
||
| 1. Setting up your first project | ||
| 2. Authentication | ||
| 3. Databases and tables | ||
| 4. Queries and pagination | ||
|
|
||
| We still don't have this guide in place, but we do have some great news. | ||
| The Appwrite docs, just like Appwrite, is completely open sourced. | ||
| This means, anyone can help improve them and add new guides and tutorials. | ||
|
|
||
| If you see this page, **we're actively looking for contributions to this page**. | ||
| Follow our contribution guidelines, open a PR to [our Website repo](https://github.com/appwrite/website), and collaborate with our core team to improve this page. | ||
| ## Prerequisites {% #prerequisites %} | ||
| 1. Android, iOS simulators, or a physical device to run the app | ||
| 2. Have [Dart](https://dart.dev/) and [Flutter SDK](https://flutter.dev/) installed on your computer | ||
| 3. Basic knowledge of Flutter and Provider. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| --- | ||
| layout: tutorial | ||
| title: Create app | ||
| description: Create a Flutter app project using Appwrite. | ||
| step: 2 | ||
| --- | ||
|
|
||
| ## Create Flutter project {% #create-flutter-project %} | ||
|
|
||
| Create a Flutter app with the `flutter create` command. | ||
|
|
||
| ```sh | ||
| flutter create ideas_tracker | ||
| cd ideas_tracker | ||
| ``` | ||
|
|
||
| ## Add dependencies {% #add-dependencies %} | ||
|
|
||
| Install the Flutter Appwrite SDK and provider. | ||
|
|
||
| ```sh | ||
| flutter pub add appwrite:17.0.0 | ||
| flutter pub add provider | ||
| ``` | ||
|
|
||
| For iOS, make sure you have CocoaPods installed. Then install the pods to complete the installation: | ||
|
|
||
| ``` | ||
| cd ios | ||
| pod install | ||
| cd .. | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| --- | ||
| layout: tutorial | ||
| title: Set up Appwrite | ||
| description: Import and initialize Appwrite for your Flutter application. | ||
| step: 3 | ||
| --- | ||
|
|
||
| ## Create project {% #create-project %} | ||
|
|
||
| Head to the [Appwrite Console](https://cloud.appwrite.io/console). | ||
|
|
||
| {% only_dark %} | ||
|  | ||
| {% /only_dark %} | ||
| {% only_light %} | ||
|  | ||
| {% /only_light %} | ||
|
|
||
| If this is your first time using Appwrite, create an account and create your first project. | ||
|
|
||
| Then, under **Add a platform**, add a Flutter platform (Android/iOS/Linux etc.) with the package/bundle ID `com.example.ideas_tracker`. | ||
|
|
||
| {% only_dark %} | ||
|  | ||
| {% /only_dark %} | ||
| {% only_light %} | ||
|  | ||
| {% /only_light %} | ||
|
|
||
| You can skip optional steps. | ||
|
|
||
| ## Initialize Appwrite SDK {% #init-sdk %} | ||
|
|
||
| To use Appwrite in our Flutter app, you'll need to find our project ID. | ||
| Find your project's ID in the **Settings** page. | ||
|
|
||
| {% only_dark %} | ||
|  | ||
| {% /only_dark %} | ||
| {% only_light %} | ||
|  | ||
| {% /only_light %} | ||
|
|
||
| Create a new file `lib/appwrite.dart` to hold our Appwrite related code. | ||
| Only one instance of the `Client()` should be created per app. | ||
| Add the following code to it, replacing `<PROJECT_ID>` with your project ID. | ||
|
|
||
| ```dart | ||
| import 'package:appwrite/appwrite.dart'; | ||
|
|
||
| final client = Client() | ||
| .setEndpoint("https://<REGION>.cloud.appwrite.io/v1") | ||
| .setProject("<PROJECT_ID>"); | ||
|
|
||
| final account = Account(client); | ||
| final db = Databases(client); | ||
| ``` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,236 @@ | ||
| --- | ||
| layout: tutorial | ||
| title: Add authentication | ||
| description: Add authentication to your Flutter application. | ||
| step: 4 | ||
| --- | ||
|
|
||
| ## User context {% #user-context %} | ||
|
|
||
| In Flutter, you can use [provider](https://pub.dev/packages/provider) for state management. | ||
|
|
||
| Create a new file `lib/providers/user_provider.dart` and add the following code to it. | ||
|
|
||
| ```dart | ||
| import 'package:appwrite/appwrite.dart'; | ||
| import 'package:appwrite/models.dart' as models; | ||
| import 'package:flutter/material.dart'; | ||
| import 'package:ideas_tracker/appwrite.dart'; | ||
|
|
||
| class UserProvider extends ChangeNotifier { | ||
| models.User? _current; | ||
|
|
||
| models.User? get current => _current; | ||
|
|
||
| UserProvider() { | ||
| init(); | ||
| } | ||
|
|
||
| Future<void> login(String email, String password) async { | ||
| try { | ||
| await account.createEmailPasswordSession( | ||
| email: email, | ||
| password: password, | ||
| ); | ||
| _current = await account.get(); | ||
| notifyListeners(); | ||
| print('Welcome back. You are logged in'); | ||
| } catch (e) { | ||
| rethrow; | ||
| } | ||
| } | ||
|
|
||
| Future<void> logout() async { | ||
| try { | ||
| await account.deleteSession(sessionId: 'current'); | ||
| _current = null; | ||
| notifyListeners(); | ||
| print("Logged out"); | ||
| } catch(e) { | ||
| rethrow; | ||
| } | ||
| } | ||
|
|
||
| Future<void> register(String email, String password) async { | ||
| try { | ||
| await account.create(userId: ID.unique(), email: email, password: password); | ||
| await login(email, password); | ||
| notifyListeners(); | ||
| print("Account created"); | ||
| } catch (e) { | ||
| rethrow; | ||
| } | ||
| } | ||
|
|
||
| Future<void> init() async { | ||
| try { | ||
| _current = await account.get(); | ||
| notifyListeners(); | ||
| } catch (e) { | ||
| _current = null; | ||
| notifyListeners(); | ||
| } | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Add the `UserProvider` to `main.dart` to make it accessible throughout the App. | ||
|
|
||
| ```dart | ||
| import 'package:flutter/material.dart'; | ||
| import 'package:ideas_tracker/providers/user_provider.dart'; | ||
| import 'package:ideas_tracker/screens/login.dart'; | ||
| import 'package:provider/provider.dart'; | ||
|
|
||
| void main() { | ||
| WidgetsFlutterBinding.ensureInitialized(); | ||
| runApp( | ||
| MultiProvider( | ||
| providers: [ | ||
| ChangeNotifierProvider(create: (_) => UserProvider()), | ||
| ], | ||
| child: const MyApp(), | ||
| ) | ||
| ); | ||
| } | ||
|
|
||
| class MyApp extends StatelessWidget { | ||
| const MyApp({super.key}); | ||
|
|
||
| @override | ||
| Widget build(BuildContext context) { | ||
| return MaterialApp( | ||
| title: "Ideas Tracker", | ||
| debugShowCheckedModeBanner: false, | ||
| home: Login() | ||
| ); | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| Now, you can use `UserProvider` to access the user's data inside any Widget. | ||
|
|
||
| ## Styling {% #styling %} | ||
|
|
||
| To maintain DRY principles, we will move all styling constants to `lib/styles.dart`. Defining these as static class members allows for consistent, reusable widget styling across the entire app. | ||
|
|
||
| ```dart | ||
| import 'package:flutter/material.dart'; | ||
|
|
||
| class Styles { | ||
| static TextStyle heading = TextStyle(fontSize: 24, fontWeight: FontWeight.w600); | ||
|
|
||
| static InputDecoration input = InputDecoration( | ||
| enabledBorder: OutlineInputBorder( | ||
| borderSide: BorderSide(color: Colors.grey), | ||
| borderRadius: BorderRadius.circular(22), | ||
| ), | ||
| focusedBorder: OutlineInputBorder( | ||
| borderSide: BorderSide(color: Colors.pink, width: 2), | ||
| borderRadius: BorderRadius.circular(22), | ||
| ), | ||
| ); | ||
|
|
||
| static ButtonStyle button = ElevatedButton.styleFrom( | ||
| backgroundColor: Colors.pinkAccent, | ||
| foregroundColor: Colors.white, | ||
| padding: EdgeInsets.symmetric(vertical: 12, horizontal: 24), | ||
| ); | ||
|
|
||
| static ButtonStyle disabledButton = ElevatedButton.styleFrom( | ||
| backgroundColor: Colors.grey, | ||
| foregroundColor: Colors.white, | ||
| padding: EdgeInsets.symmetric(vertical: 12, horizontal: 24), | ||
| ); | ||
| } | ||
| ``` | ||
|
|
||
| ## Login page {% #login-page %} | ||
|
|
||
| Create a new file `lib/screens/login.dart` and add the following code to it. | ||
| this page contains a basic form to allow the user to login or register. | ||
| Notice how this page utilizes the `UserProvider` to perform login and register actions. | ||
|
|
||
| ```dart | ||
| import 'package:flutter/material.dart'; | ||
| import 'package:ideas_tracker/providers/user_provider.dart'; | ||
| import 'package:ideas_tracker/styles.dart'; | ||
| import 'package:provider/provider.dart'; | ||
|
|
||
| class Login extends StatefulWidget { | ||
|
|
||
| const Login({super.key}); | ||
|
|
||
| @override | ||
| State<Login> createState() => _LoginState(); | ||
| } | ||
|
|
||
| class _LoginState extends State<Login> { | ||
| final TextEditingController _emailController = TextEditingController(); | ||
| final TextEditingController _passwordController = TextEditingController(); | ||
|
|
||
| @override | ||
| void dispose() { | ||
| _emailController.dispose(); | ||
| _passwordController.dispose(); | ||
| super.dispose(); | ||
| } | ||
|
|
||
| @override | ||
| Widget build(BuildContext context) { | ||
| return Scaffold( | ||
| body: SafeArea( | ||
| child: Padding( | ||
| padding: EdgeInsets.symmetric(horizontal: 40), | ||
| child: Column( | ||
| mainAxisAlignment: MainAxisAlignment.center, | ||
| children: [ | ||
| Text("Login or Register", style: Styles.heading), | ||
| SizedBox(height: 40,), | ||
| TextField( | ||
| controller: _emailController, | ||
| decoration: Styles.input.copyWith( | ||
| hintText: "Email" | ||
| ), | ||
| ), | ||
| SizedBox(height: 25), | ||
| TextField ( | ||
| controller: _passwordController, | ||
| decoration: Styles.input.copyWith( | ||
| hintText: "Password" | ||
| ), | ||
| ), | ||
| SizedBox(height: 25), | ||
| Row( | ||
| mainAxisAlignment: MainAxisAlignment.center, | ||
| children: [ | ||
| ElevatedButton( | ||
| onPressed: () { | ||
| context.read<UserProvider>().login( | ||
| _emailController.text, | ||
| _passwordController.text, | ||
| ); | ||
| }, | ||
|
greptile-apps[bot] marked this conversation as resolved.
|
||
| style: Styles.button, | ||
| child: Text("Login") | ||
| ), | ||
| SizedBox(width: 24), | ||
| ElevatedButton( | ||
| onPressed: () { | ||
| context.read<UserProvider>().register( | ||
| _emailController.text, | ||
| _passwordController.text, | ||
| ); | ||
| }, | ||
| style: Styles.button, | ||
| child: Text("Register"), | ||
| ), | ||
| ], | ||
| ) | ||
| ]), | ||
| ), | ||
| ) | ||
| ); | ||
| } | ||
| } | ||
| ``` | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.