How to use the different content types in Appwrite functions

Demola Malomo - Oct 24 '23 - - Dev Community

The technology landscape is constantly changing with new and innovative solutions. This has allowed the industry to utilize modern engineering solutions such as Docker, Kubernetes, managed services, and serverless functions when building and deploying software.

In fact, serverless functions have leveled the playing field by giving developers the required flexibility to build and ship software fast at a reduced cost and with good developer experience.

In this post, we will explore how Appwrite is changing the game through its serverless function offering and support for multiple content types; we’ll also demonstrate how to use it in a Flutter application.

GitHub links

The project source codes are below:

Prerequisites

To fully grasp the concepts presented in this tutorial, the following are required:

  • Basic understanding of Dart and Flutter
  • GitHub account
  • Appwrite Cloud account. Sign-up is free.

Serverless functions on Appwrite

Appwrite provides a unique approach to managing and hosting serverless functions within either a secure cloud or self-hosted environment, offering an excellent user experience for developers. In addition, it also lets developers:

  • Customize and extend code capabilities
  • Simplify deployment complexities
  • Effectively address scalability concerns

Beyond its extensibility, security features, and other valuable capabilities, Appwrite also facilitates the return of various content types as a response, such as JSON, HTML, Text, and more. This capability is transformative and a game-changer because it allows developers to extend their use cases and unlock new possibilities.

Below are the content types supported by Appwrite and the sample use case:

Empty

This response type in a serverless function does not yield any output when used in a function. A sample use case is a function responsible for initializing the database and corresponding collections.

import 'dart:async';

Future<dynamic> main(final context) async {
  //call to another function can come here
  return context.res.empty();
}
Enter fullscreen mode Exit fullscreen mode

Redirect

This type is used to redirect users to another URL. A sample use case is a function that redirects users to a specified URL.

import 'dart:async';
import 'dart:convert';

Future<dynamic> main(final context) async {
  final data = jsonDecode(context.req.body);
  return context.res.redirect(data["url"], 301);
}
Enter fullscreen mode Exit fullscreen mode

JSON

This type returns a JSON. A sample use case is a function that uploads files into storage and returns the upload information.

import 'dart:async';

Future<dynamic> main(final context) async {
  //upload to storage code
  return context.res.json({"upload_id" "erjkdkkSAMPLE_UPLOAD_2343ujej"});
}
Enter fullscreen mode Exit fullscreen mode

HTML

This type returns HTML content. A sample use case is a function designed to manage an RSVP form.

import 'dart:async';

Future<dynamic> main(final context) async {
  final html = '''<!doctype html>
      <html lang="en">
      <head>
        <meta charset="utf-8">
        <title>Contact Form</title>
      </head>
      <body>
        <form action="/" method="POST">
          <input type="email" id="email" name="email" placeholder="Email" required>
          <button type="submit">Submit</button>
        </form>
      </body>
      </html>
      ''';

  return context.res.send(html, 200, {'content-type': 'text/html'});
}
Enter fullscreen mode Exit fullscreen mode

Text

This type returns a text. A sample use case is a function designed to verify the operational status of a service.

import 'dart:async';

Future<dynamic> main(final context) async {
  //api check to a service
  return context.res.send("The service is running!");
}
Enter fullscreen mode Exit fullscreen mode

Now that we understand Appwrite’s serverless function and its support for multiple content types, we can build our application and test multiple responses.

Create Appwrite’s serverless function to get multiple content types

Set up a project on Appwrite

To get started, we need to create a project on Appwrite’s console to deploy and test our function. To do this, log into the Appwrite console, click the Create project button, input appwrite_content_type as the name, and Create.

Create project

Secondly, we must navigate to the Functions tab and create a function using the Dart template.

Create using dart template

Input appwrite_content_type as the name and continue.

Create function name

Check the Generate API key on completion option so that our function can securely connect to Appwrite and proceed.

Set variables

Check the Create a new repository option and connect to GitHub by following the prompt.

repo type
Connect to GitHub

On connection, input appwrite_content_type as the repository name, accept the default branch suggestion and Create.

Repository name
Accept branch details

With that done, we should see our deployed function on Appwrite and the corresponding source code on GitHub.

source code
Deployed on Appwrite

Modify the generated source code on GitHub

Next, we must modify the generated function source code on GitHub by navigating to the lib/main.dart file, click the Edit icon, and add the snippet below:

Edit main.dart

import 'dart:async';
import 'dart:convert';

Future<dynamic> main(final context) async {
  final data = context.req.headers;
  final responseType = json.encode(data['Content-Type']);

  switch (responseType) {
      case 'application/json':
        return context.res.json(
            {'type': 'This is a sample JSON response from Appwrite function'});
      case 'text/html':
        return context.res.send(
            '<h1>This is a sample HTML response from Appwrite function</h1>',
            200,
            {'content-type': 'text/html'});
      default:
        return context.res
            .send('This is a sample text response from Appwrite function');
    }
}
Enter fullscreen mode Exit fullscreen mode

The snippet above gets the request header based on a header and conditionally returns a matching response type.

Lastly, we must commit the changes by clicking the Commit changes… button, input appropriate commit message, and save.

Commit changes

With that done, Appwrite will automatically redeploy our function using the updated source code.

Latest deployment

Leveraging deployed function in a Flutter application

To get started, we need to clone the project by navigating to the desired directory and running the command below:

git clone https://github.com/Mr-Malomz/flutter_appwrite_content
Enter fullscreen mode Exit fullscreen mode

Running the project

We need to install the project dependencies by running the command below:

flutter pub get
Enter fullscreen mode Exit fullscreen mode

Then, run the project using the following command:

flutter run
Enter fullscreen mode Exit fullscreen mode

The command above will run the application on the selected device.

Home screen

Add platform support and update function permission

To securely connect our Flutter application with Appwrite, we need to add it as a supported platform on the console. To do this, navigate to the Overview menu and click the Flutter button.

Add platform

Next, we must modify the Flutter application as detailed below:

To obtain our Bundle ID, navigate to the path below:
ios > Runner.xcodeproj > project.pbxproj

Open the project.pbxproj file and search for PRODUCT_BUNDLE_IDENTIFIER.

IOS Flutter app

To obtain our package name for Android, navigate to the path below:
android > app > src > debug > AndroidManifest.xml

Open the AndroidManifest.xml file and copy the package value.

Android Flutter App

We must also modify the AndroidManifext.xml as shown below:

<manifest>
  <application>
    <activity android:name="com.linusu.flutter_web_auth_2.CallbackActivity" android:exported="true">
      <intent-filter android:label="flutter_web_auth_2">
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="appwrite-callback-[PROJECT_ID]" />
      </intent-filter>
    </activity>
  </application>
</manifest>
Enter fullscreen mode Exit fullscreen mode

We must also replace the [PROJECT_ID] section with our actual Appwrite project ID. We can get our project ID from the Overview tab.

Project ID

Lastly, we must update the deployed function permission to use it in our Flutter application. To do this, navigate to the Settings tab of the deployed function, scroll to the Execute Access section, select Any, and Update.

function permission

PS: Appwrite ships with a robust role and access management to cater to individual and company needs.

Create a service

With that done, we need to create a service file to separate the application core logic from the UI. To do this, create a utils.dart file inside the lib directory. Then, add the snippet below:

import 'package:appwrite/appwrite.dart';

class _AppConstant {
  final String endpoint = "https://cloud.appwrite.io/v1";
  final String projectId = "REPLACE WITH PROJECT ID";
  final String functionId = "REPLACE WITH FUNCTION ID";
}

class ContentService {
  Client _client = Client();
  ContentService() {
    _init();
  }

  //initialize the application
  _init() async {
    _client
        .setEndpoint(_AppConstant().endpoint)
        .setProject(_AppConstant().projectId);

    //get current session
    Account account = Account(_client);

    try {
      await account.get();
    } on AppwriteException catch (e) {
      if (e.code == 401) {
        account
            .createAnonymousSession()
            .then((value) => value)
            .catchError((e) => e);
      }
    }
  }

  Future getContentType(String selectedType) async {
    Map<String, String> contentTypes = {
      "text": "text/plain",
      "json": "application/json",
      "html": "text/html"
    };
    Functions functions = Functions(_client);
    try {
      var result = await functions.createExecution(
        functionId: _AppConstant().functionId,
        headers: {'Content-Type': contentTypes[selectedType]},
      );
      return result.responseBody;
    } catch (e) {
      throw Exception('Error creating subscription');
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The snippet above does the following:

  • Creates a private class _AppConstant to save required properties
  • Creates a ContentService class with _client and _account properties to connect to the Appwrite instance
  • Creates an _init method that configures Appwrite using the properties
  • Creates a getContentType method that uses the _client property to create an instance of the function and then use the createExecution method to trigger the function with the required parameters

Consuming the service

First, we need to modify the home.dart file inside the screens folder by importing the required dependencies and using the service to perform required operations:

import 'package:flutter/material.dart';
import 'package:flutter_appwrite_content/utils.dart';
class Home extends StatefulWidget {
  const Home({super.key});
  @override
  State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
  final _formKey = GlobalKey<FormState>();
  var _selected = '';
  var _dropdownItems = ["text", "json", "html"];
  bool _isLoading = false;
  var _contentValue = '';

  _handleContentType() {
    setState(() {
      _isLoading = true;
      _contentValue = '';
    });
    ContentService().getContentType(_selected).then((value) {
      setState(() {
        _isLoading = false;
        _contentValue = value.toString();
      });
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Content retrieved successfully!')),
      );
    }).catchError((e) {
      setState(() {
        _isLoading = false;
      });
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Error retrieving content!')),
      );
    });
  }

  Widget build(BuildContext context) {
    //UI code goes here
  }
}
Enter fullscreen mode Exit fullscreen mode

The snippet above does the following:

  • Imports the required dependency
  • Line 12-13: Creates the _isLoading and _contentValue properties to manage application state and results from the service, respectively
  • Line 15-36: Creates an _handleContentType method that uses the getContentType service to get the required content

Lastly, we must update the UI to use created properties to manage the application state and display returned content.

//import goes here

class Home extends StatefulWidget {
  const Home({super.key});
  @override
  State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
  //state goes here

  _handleContentType() {
    //code goes here
  }

  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        title: Text("Appwrite Function Content"),
        backgroundColor: Colors.black,
      ),
      body: Padding(
        padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 30.0),
        child: Column(
          children: [
            Form(
              key: _formKey,
              child: Column(
                children: [
                  Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      //title
                      Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          const Text(
                            'Data type',
                            style: TextStyle(
                              color: Colors.grey,
                              fontSize: 14.0,
                            ),
                          ),
                          const SizedBox(height: 5.0),
                          DropdownButtonFormField(
                            items: _dropdownItems.map((String item) {
                              return DropdownMenuItem(
                                value: item,
                                child: Text(item),
                              );
                            }).toList(),
                            onChanged: (value) {
                              setState(() => _selected = value!);
                            },
                            decoration: InputDecoration(
                              contentPadding: const EdgeInsets.symmetric(
                                  vertical: 10, horizontal: 20),
                              hintText: "select data type",
                              focusedBorder: OutlineInputBorder(
                                borderRadius: BorderRadius.circular(10),
                                borderSide:
                                    const BorderSide(color: Colors.grey),
                              ),
                              enabledBorder: OutlineInputBorder(
                                borderRadius: BorderRadius.circular(10),
                                borderSide:
                                    const BorderSide(color: Colors.grey),
                              ),
                              errorBorder: OutlineInputBorder(
                                borderRadius: BorderRadius.circular(10),
                                borderSide: const BorderSide(color: Colors.red),
                              ),
                            ),
                          ),
                        ],
                      ),
                    ],
                  ),
                  const SizedBox(height: 30.0),
                  SizedBox(
                    height: 45,
                    width: double.infinity,
                    child: TextButton(
                      onPressed: () {
                        if (_formKey.currentState!.validate()) {
                          _handleContentType();
                        }
                      },
                      style: ButtonStyle(
                        backgroundColor:
                            MaterialStateProperty.all<Color>(Colors.black),
                      ),
                      child: const Text(
                        'Get content',
                        style: TextStyle(
                          color: Colors.white,
                          fontWeight: FontWeight.bold,
                          fontSize: 14.0,
                        ),
                      ),
                    ),
                  ),
                ],
              ),
            ),
            const SizedBox(height: 40.0),
            Container(
              width: MediaQuery.of(context).size.width,
              height: 200.0,
              padding: EdgeInsets.all(10),
              decoration: BoxDecoration(
                border: Border.all(color: Colors.blueAccent),
                borderRadius: BorderRadius.circular(10),
              ),
              child: Text(_contentValue),
            )
          ],
        ),
      ),
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

With that done, we restart the application using the code editor or run the command below:

flutter run
Enter fullscreen mode Exit fullscreen mode

Conclusion

This post discussed the support for multiple content types in Appwrite function and how to use it in a Flutter application. In addition to the use cases mentioned above, Appwrite's support for multiple content types unlocks exciting possibilities for developers by allowing them to dynamically build various types of applications.

These resources may also be helpful:

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