Appwrite cloud functions, Flutter and private GitLab repos

From start to finish

Danielle H
9 min readJun 7, 2024

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?

How?

So here it is, step by step from start to finish.

Table of contents

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.

First time you can create a new repo, after that you can add to the existing repo if you want.

Specify repo of your new function code.

If creating a new repo
If adding to an existing repo

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.

Deployments page

If a deployment fails, you can check the logs and see what caused the error.

Logs
Example of error in logs

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:

Light gray

When the function is scheduled to run every minute, it looks like this:

Just a little blacker.

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.

  1. On the left sidebar, select Search or go to and find your project (common_code).
  2. Select Settings > Repository.
  3. Expand Deploy tokens.
  4. Select Add token.
  5. Complete the fields, and select the desired scopes. We want read_repository.
  6. Select Create deploy token.
  7. 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.

--

--

Danielle H

I started programming in LabVIEW and Matlab, and quickly expanded to include Android, Swift, Flutter, Web(PHP, HTML, Javascript), Arduino and Processing.