Appwrite cloud functions, Flutter and private GitLab repos
From start to finish
How to write a cloud function in Dart that uses code from a private GitLab repo?
Appwrite is an awesome Backend-as-a-Service. Most apps require some kind of cloud function — to update an index, perform server-side functions that need secure API keys, etc. And Appwrite even supports cloud functions written in Dart!
My app is written in Flutter, I have models, enums and constants that are used in the mobile app (that creates the data) the desktop app (that reads the data) and the cloud functions (that perform actions on the data). I really don’t want to copy-paste code, it just leads to unmaintainable code that is open to all kinds of bugs.
So a package of common code seems to be the best solution. But cloud functions work in isolated containers. How to give them access to your private package on Gitlab? What can be put in the package? How to configure Appwrite so that your passwords aren’t hardcoded?
So here it is, step by step from start to finish.
Table of contents
- Step 1: Create your package
- Step 2: Create a cloud function in Appwrite
- Step 3: Add your private package to the function
- Step 4: Create project deployment token
- Step 5: Add token to cloud function
- Step 6: Configure Appwrite function
- Step 7: Deploy
- Troubleshooting
- Summary
Step 1: Create your package
There are step-by-step instructions here. An important point: Cloud functions are not in Flutter, they are in Dart. So only pure Dart code can be in your common repo. This means you need to change the default pubspec.yaml
of your package.
(I am pretty sure that it’s possible to can configure the build settings below to run Flutter code if you really want to, but let’s leave that for another post, OK?)
Standard Flutter app pubspec.yaml
looks like this
name: common_code
description: "package containing common code"
publish_to: "none" # Remove this line if you wish to publish to pub.dev
version: 1.0.0
environment:
sdk: ">=3.3.0 <4.0.0"
dependencies:
flutter:
sdk: flutter
dev_dependencies:
flutter_lints: ^3.0.0
flutter_test:
sdk: flutter
# The following section is specific to Flutter packages.
flutter:
uses-material-design: true
You need to remove anything connected to Flutter, and leave only the “generic Dart” part of the file. In addition, as of writing this, Appwrite cloud functions use Dart 3.1.5, so you also need to change the environment
to support this. So now it looks like this:
name: common_code
description: "package containing common code"
version: 0.0.4
homepage:
publish_to: "none" # Remove this line if you wish to publish to pub.dev
environment:
sdk: ">=3.1.5 <4.0.0" # note the 3.1.5 here
dependencies: #removed all flutter here
dev_dependencies: # and here
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# remove all flutter related stuff
Any code that needs Flutter stuff cannot be used in the cloud function.
Push your package to your private GitLab repo.
Step 2: Create a cloud function in Appwrite
In Appwrite, go to Functions in left sidebar. There, click on Create function
.
In the next screen, choose the Dart starter template.
Pick a name for your function and press Next.
Add a variable if you want to access your database from the function:
Connect to a repo in GitHub where the function code will be. The first time, you will need to connect Appwrite to your GitHub account and allow it access to all your repos.
Note that the appwrite cloud function code will be in GitHub. Your common package code is in GitLab.Currently Appwrite doesn’t yet support Gitlab for cloud functions.
You can create a separate repo for each function, or put them all in one repo. In the next step, you can specify an internal folder in the repo that will hold this specific code.
I personally have one repo for all the functions, each function is in its own folder.
Specify repo of your new function code.
Pick a branch and folder for your function code.
And done.
Of course, at this point, your function is the template function. To actually do something that you want, you’ll need to change the code. Clone the new repo that Appwrite created for you and try out all kinds of things. Full documentation in the Appwrite docs.
Important: Change the name of your new function from starter_template
to something else.
Pay careful attention to the following:
Deployments
The code itself is a Deployment. Every time you push code to the branch you specified above, Appwrite compiles your code (without running it). This can succeed or fail, so look carefully at the Deployment page.
If a deployment fails, you can check the logs and see what caused the error.
Executions
This is the actual run of your function. This can also fail, and this also has logs. Keep in mind that in the free tier, logs are saved for only 1 hour.
Usage
This will show you how many times your function was called. Keep an eye on it and make sure that it's running exactly as often as you want it, and no more, as this can cost money (Starter plan is up to 750K executions a month).
Settings
We will be back here later, but in the meantime note the following:
Execute access — who can run this function using the client API? If it’s a function that should run every day at 20:00 (or be triggered by an event, see below), then it should be empty. If it can be run only by authenticated users, then change this to “All Users”, etc.
Events — You can trigger your function to occur every time a new document is created in your database, or an image is deleted from your storage, or a user logs in.
Schedule — Use cron syntax to schedule your function to a specific time or period, e.g. every day at 20:00.
Warning: when the schedule is empty, it looks like this:
When the function is scheduled to run every minute, it looks like this:
Be careful and make sure you completely erase the schedule if you no longer want it!
Step 3: Add your private package to the function
Add it to pubspec.yaml
, under dependencies
:
dependencies:
common_code:
git:
url: https://gitlab.com/your-namespace/common-code.git
ref: main
Check that you can use your constants etc. from your function code. It will probably work on your local machine as is because you already have access to your private repo, or VSCode (or whatever IDE you like) will ask you for credentials.
You can push your function to GitHub if you like, and watch the deployment fail with the following error:
Git error. Command: `git clone --mirror https://gitlab.com/your-namespace/common-code.git
stdout:
stderr: Cloning into bare repository
fatal: could not read Username for 'https://gitlab.com': No such device or address
exit code: 128
Step 4: Create project deployment token
We need to find a way for Appwrite to perform authentication to your private repo. The solution is to create a deploy token.
You can use a deploy token to enable authentication of deployment tasks, independent of a user account. In most cases you use a deploy token from an external host, like a build server or CI/CD server.
- On the left sidebar, select Search or go to and find your project (common_code).
- Select Settings > Repository.
- Expand Deploy tokens.
- Select Add token.
- Complete the fields, and select the desired scopes. We want
read_repository
. - Select Create deploy token.
- Save the name and token, because you cannot access it again. I save to BitWarden.
Step 5: Add token to cloud function
This is where things get a bit tricky.
The simplest option is to hardcode your token name and password directly into your function’s pubspec.yaml
:
dependencies:
common_code:
git:
url: https://<deploy_token_name>:<deploy_token_password>@gitlab.com/your-namespace/common-code.git
ref: main
I seriously considered doing this, as the function repo is private. But it made my fingers itch, because it’s never good to hardcode secret passwords.
The recommended method is to use environment variables. But using environment variables directly in pubspec.yaml
is not possible at this time.
So the workaround is to create a Dart script that takes the environment variables and injects them into pubspc.yaml
, and run this script before running dart pub get
during the deployment.
So, create a file called update_pubspec.yaml
. Place it next to pubspec.yaml
in the root folder.
import 'dart:io';
void main() {
final gitlabUsername = Platform.environment['GITLAB_USERNAME'];
final gitlabToken = Platform.environment['GITLAB_TOKEN'];
if (gitlabUsername == null || gitlabToken == null) {
print('GITLAB_USERNAME and GITLAB_TOKEN must be set.');
exit(1);
}
final pubspec = File('pubspec.yaml');
final pubspecContent = pubspec.readAsStringSync();
final newContent = pubspecContent.replaceFirst(
'https://gitlab.com/your-namespace/common-code.git',
'https://$gitlabUsername:$gitlabToken@gitlab.com/your-namespace/common-code.git'
);
pubspec.writeAsStringSync(newContent);
print('pubspec.yaml has been updated with the GitLab deploy token.');
}
This gets the environment variables (which we haven’t created yet) and replaces the unauthenticated location of your private repo with the authenticated version.
You can push the code now if you like, the deployment will fail because we haven’t configured Appwrite yet.
Step 6: Configure Appwrite function
Phew! Now we need to add the environment variables and instruct Appwrite to run our script.
Environment variables
In Appwrite console, go to your function → Settings → Environment variables. Click Create variable. Add the token name as GITLAB_USERNAME and the token code as GITLAB_TOKEN.
Build Settings
In your function → Settings → Configuration, expand the Build settings. It will show the default command, which is dart pub get. We want to run update_pubspec
before this, so replace it with the following:
dart run update_pubspec.dart &&
dart pub get
Multiple commands must be chained with the && operator.
Don’t forget to press “Update”.
Step 7: Deploy
If you haven’t pushed your function code yet, do it now. It will trigger a deployment. If you already have, simply press redeploy
.
Wait patiently, it should succeed :)
Troubleshooting
If you see the following errors in the deployment logs, this is what you should do:
GITLAB_USERNAME and GITLAB_TOKEN must be set
You forgot to set the environment variables, or you spelled them wrong.
Could not find package `update_pubspec.dart` or file `update_pubspec.dart`
Your update_pubspec.dart
isn’t in the correct place, or it’s spelled wrong...
OR the function code isn’t updated. Make sure to change the name of your function from starter_template
and make sure the line
found user package my_package
appears in the logs and not starter_template
.
If the code is not updated, try to deploy again.
Summary
A bit long, but this way you can use common code in all aspects of your app — both frontend and backend.
All images captured using GreenShot and Upscayl.
Hoping against hope, 244 days. #BringThemHomeNow.
Check out my free and open source online game Space Short. If you like my stories and site, you can also buy me a coffee.