How to create a private image upload using Appwrite and Flutter

Femi-ige Muyiwa - Dec 29 '22 - - Dev Community

Public image libraries such as Unsplash have been a breath of fresh air for content creators, enabling access to tons of copyright-free images. Sometimes, though, creators prefer privacy content and want to gatekeep "good" things. This article covers how to securely upload private images to Appwrite using Flutter.

This article demonstrates the following:

  • User registration and login using Appwrite
  • State management using the provider package in Flutter
  • Picking images from storage using the file_picker package in Flutter and uploading the images to Appwrite
  • Setting document-level permission within Appwrite
  • Obtaining the images from Appwrite and updating the UI

If you want to jump straight into the project, you can clone the repository containing the code for the project.

Prerequisites

The following requirements apply to follow along:

Setting up the Appwrite project

After setting up an Appwrite instance, head to your browser and type in your IP address or hostname. Next, select Create Project and fill in your desired project name and ID.

Note: ID can be auto-generated

create project

After creating a project, scroll down within the Appwrite console and select create platform. When you get a popup, select Flutter and choose the operating system you plan to work on
(in this case, choose Android). Next, specify the application and package names (you can find the packaging in your app-level build.gradle file).

Create platform
Create platform

After creating a platform, head to storage to create a bucket. To do that, click on add bucket, then set the bucket-level permission's read and write access to allow all users (role:all).

create bucket 1
create bucket 2
create bucket 3

Unlike the other client-side SDKs, the Flutter SDK does not return a URL when previewing a storage file from Appwrite. In this tutorial, you will use a database to store the URL of every new image file upload. Head to the database section and select create database.

Create database

Next, we’ll create a collection. When allowing public access to a collection, use collection-level permissions, preferably the permission role:all. In this case, use document-level permission, as this allows all the users to share a single collection, but they have access to their documents only. Now, let’s create a required string attribute called url to store the manually generated URL of each image added to the storage.

Create collection
collection permission

Cloning the Flutter UI template and connecting to Appwrite

This section uses a UI template containing user registration and login code. To get it, clone the repository specified in the prerequisites. Check out the official GitHub docs to learn more about cloning a repository.

cloning repo
cloning repo

If you're not new to Appwrite, you can proceed to the next step, but for new readers, this is how to connect a Flutter project to Appwrite for Android and iOS devices.

iOS
First, obtain the bundle ID by going to the project.pbxproj file (ios > Runner.xcodeproj > project.pbxproj) and searching for the PRODUCT_BUNDLE_IDENTIFIER.

Now, head to the Runner.xcworkspace folder in the applications iOS folder in the project directory on Xcode. To select the runner target, choose the Runner project in the Xcode project navigator and then find the Runner target. Next, select General and IOS 11.0 in the deployment info section as the target.

Android
To do this, copy the XML script below and paste it below the activity tag in the Androidmanifest.xml file (to find this file, head to android > app > src > main).

Note: change [PROJECT-ID] to the ID you used when creating the Appwrite project

Getting Started

When you run the cloned repo, you should have a UI that looks like the image below:

Cloned repo UI

Above is a homepage section with an elevated button that satisfies multiple commands depending on the condition. These conditions range from checking if a user is logged in and redirecting you to a login page if false to selecting an image(s) from your local storage, uploading it to Appwrite, and displaying the images in a grid.

Currently, the template lacks most of the functionality mentioned above. Thus, start by updating two folders (constants and model) within the lib folder with some new files. Starting with the constants folder, create a file called app_constants.dart, which is required to store some constants that you will use later. Start by pasting the code below into the file:

Image upload
Next, we’ll create two model classes similar to the one in the template folder. Let’s use the first model class to map out the structure of the storage bucket, as we’ll manually create a URL. Thus, there are two essential data from every newly uploaded file: the bucketID and the uploaded file's ID.

To use the model class above, we need to be able to pick a file from your local storage and upload it to Appwrite. Thus, head to the privateprovider.dart file and call the Appwrite storage API, the model class created earlier, and a PlatformFile class from the file_picker package within the ChangeNotifier class.

Next, create a getter for the model class, then set the value for the model class and the PlatformFile class to null(initializers). Also, remember to initialize the Appwrite's storage API. Next, create a function to select images from local storage and call it within the conditional statement in the checklogin function.

Next, create a function to upload the selected file from the local storage to the Appwrite storage bucket. In the function, we’ll need to map the JSON result to the model class using the jsonEncode and jsonDecode functions provided by the dart convert package. Finally, call the function within the filepicker function, and it should look like this:

Image retrieval
To retrieve the images from the storage bucket, we need to use the database created earlier and create another model class to map the list of documents from the database. Thus, a model class like the one below is required:

Next, call the Appwrite database API and the model class above. Then, create a getter for the model class and initialize the database API.

After uploading the image to a storage bucket, we’ll "convert" the image into a URL. To do that, create another function, and within the function, create a variable with the value of a query string created from using the endpoint, bucketid, fileid, and projectid. Next, initialize the Appwrite database createDocument method and set the data to the variable you created. Call the function in the file picker function so it can trigger the function automatically after uploading the files to the storage bucket.

Now, we’ll create another function to display the images using the listDocuments method from the database function. Then map through the list of documents with respect to the model class created earlier. For this section, we need to be able to display the images once a user logs in or once they upload a new image. Thus, call the function in the _init, login, and createdocument functions.

Now, the provider is ready for use, and all that is left is updating the home.dart file with the code below:

The code above consists of a button that triggers a function linked to the checklogin function within the ChangeNotifier class (PrivateProvider). The body uses a GridView.builder, which creates a grid of containers with respect to the number of items in a list. In this case, it is the list of documents in the database. The containers are decorated using a NetworkImage using the URL value obtained from the database collection.

result

Conclusion

This article explains how to use the Appwrite service to register and log in users as well as how to create document-level access to allow users to have permission to access documents particular to them alone. Using that idea, we’ve built a private image upload using the filepicker package and Appwrite.

For further reading, here is a resource you might find helpful:

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .