Introduction to Strapi Media Library
Tags: opensource, testing, unittest, beginners
Published: 2024-05-31 10:45:14

Strapi is an open-source, Node.js-based, Headless Content Management System(CMS) that allows you to streamline the process of managing content, giving you the freedom to use your favorite tools and frameworks. It has a Media Library plugin that allows you to upload, save, and manage different media files such as images, videos, and documents.

In this tutorial, you'll learn how to build a media gallery application using Flutter and Strapi's Media Library plugin.

## Prerequisites
You will need the following to get started:

- [Node.js v18](https://nodejs.org/) or later.
- A code editor on your computer.
- [Node.js package manage](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm/)r v6 and above.
- [Python](https://www.python.org/downloads/)
- Set up your [Flutter environment](https://flutter.dev/docs/get-started/install) if you haven't already.

The code for this tutorial is available on my GitHub [repository](https://github.com/icode247/flutter_media_app/). Feel free to clone and follow along.


## Setting up Strapi with the Media Library
### Strapi Installation
Start by creating a new Strapi backend project by running the command below:

```bash
npx create-strapi-app@latest media-app --quickstart
```
The above command will scaffold a new Strapi content management system project and install the required Node.js dependencies.

Fill out the forms to create your administrator user account.

### Install Strapi Media Library Plugin
Go back to your terminal, and quit the server with `Ctrl+ C`. Navigate to the project directory and enable the [Media Libray Plugin](https://strapi.io/features/media-library) by running the command below:

```bash
cd media-app
npm i @strapi/plugin-upload
```
Update your `config/plugin.js` file to configure the upload plugin. There are many plugins in the Strapi marketplace for uploads including Firebase storage, Cloudflare, etc. But there are only 3 providers maintained by Strapi which are Local, Amazon S3, and Cloudinary. For this demonstration, you'll use the [Local plugin](https://docs.strapi.io/dev-docs/plugins/upload):

```js
module.exports = ({ env }) => ({
  upload: {
    config: {
      providerOptions: {
        localServer: {
          maxage: 300000,
        },
      },
    },
  },
});

```

### Enable Public Access for Upload API
In order to be able to perform some operations on the upload plugin, we need to enable public access.

From the left sidebar, click on **Settings**. Again,  on the left panel under **USERS & PERMISSIONS PLUGIN**, click on **Roles**, then click on **Public** from the table on the right. Now scroll down, click on `Upload`, and tick Select all. After that click the "Save" button.

![001-enable-api-access.png](https://api-prod.strapi.io/uploads/001_enable_api_access_0fe06d4348.png)

## Uploading Media Files to Strapi
Back to your admin panel, in the left sidebar, click on the **Media Library** section. Then click **Add new assets** -> **FROM COMPUTER** -> **Browse Files** and you'll be prompted to upload files from your local computer. Select at least three files and click **Upload assets to library** to upload them.

## Creating a Flutter Project
Open a new tab on your terminal and create a new Flutter application by running the command below:

```bash
flutter create media_library_app
```

Once the project is created, open it in your IDE or code editor. Change the directory to the project folder and run the application with the following commands:

```bash
cd media_library_app
flutter run
```

## Retrieving Media Files from Flutter
In your projects `lib` directory, create a `features/services` directory. Create a `strapi_service.dart` file in the `services` directory and add the code to fetch all the assets you added to your Media Library:

```js
import 'dart:convert';
import 'package:http/http.dart' as http;

class StrapiService {
  static const String baseUrl = 'http://localhost:1337';

  Future<List<dynamic>> getMediaFiles() async {
    final response = await http.get(Uri.parse('$baseUrl/api/upload/files'));
    if (response.statusCode == 200) {
      final jsonData = json.decode(response.body);
      return jsonData;
    } else {
      throw Exception('Failed to load media files');
    }
  }
}

```
This service class provides a `getMediaFiles` method that makes a `GET` request to the Strapi server's `/upload/files` endpoint to return the list of media files.

For the above code to work, open the `pubspec.yaml` file and add the `http`,  `file_picker` and `http_parser` module in the dependency section to allow you send API requests to your Strapi backend, select images from your device file system and to parse the HTTP response:


```yaml
dependencies:
  flutter:
    sdk: flutter
  http: ^0.13.5
  file_picker: ^8.0.3
  http_parser: ^4.0.2
```
Then run the command below to install the dependency:

```bash
flutter pub get
```
## Displaying Media Files in Flutter
Create a new directory called `screens` in the `lib/features` directory to create our Flutter UI. Then create a `home_screen.dart` file inside the `screens` folder and add the code snippets below:

```js
import 'package:flutter/material.dart';
import 'package:media_library_app/features/services/strapi_service.dart';

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  List<dynamic> mediaFiles = [];
  StrapiService strapiService = StrapiService();

  @override
  void initState() {
    super.initState();
    fetchMediaFiles();
  }

  Future<void> fetchMediaFiles() async {
    try {
      final files = await strapiService.getMediaFiles();
      setState(() {
        mediaFiles = files;
      });
    } catch (e) {
      print('Error fetching media files: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Strapi Media Library'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: ListView.builder(
          itemCount: mediaFiles.length,
          itemBuilder: (context, index) {
            final file = mediaFiles[index];
            final imageUrl = StrapiService.baseUrl + file['url'];
            return ListTile(
              title: Text(file['name']),
              subtitle: Text(file['mime']),
              leading: Image.network(imageUrl),
            );
          },
        ),
      ),
    );
  }
}
```
The above code will:
- Create a `HomePage StatefulWidget` class to allow you to handle the application state on this screen.
- Create `mediaFiles` empty array to store the media files data and create an instance of the `StrapService` class to allow you access to the methods in the class.
- Add a `fetchMediaFiles()` method to call the `strapiService.getMediaFiles()` method to retrieve the media from Strapi Media Library and call the `setState` function to modify the app's state and set the retrieved files data to the `mediaFiles` array. 
- Call the `initState` method to execute the code in `fetchMediaFiles()` when the widget is first created.
- Render the media data using the `ListView` widget.

Now update your `main.dart` file to render the `HomePage` widget in your Flutter UI:

```js
import 'package:flutter/material.dart';
import 'package:strapi_media_app/features/screens/home_screen.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

```


![002-image-media-library.png](https://api-prod.strapi.io/uploads/002_image_media_library_f1d4486c9e.png)

## Handling Media Uploads from Flutter
Next, update the code in your `lib/features/services/strapi_service.dart` file to add a new method to send a **POST** request to the `/upload` endpoint to upload new files to the Media Library.
    
```js
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart'; // Import the http_parser package

class StrapiService {
  static const String baseUrl = 'http://localhost:1337';

  Future<List<dynamic>> getMediaFiles() async {
    try {
      final response = await http.get(Uri.parse('$baseUrl/api/upload/files'));
      if (response.statusCode == 200) {
        final jsonData = json.decode(response.body);
        return jsonData;
      } else {
        throw Exception('Failed to load media files');
      }
    } catch (e) {
      throw Exception(e);
    }
  }

  Future<dynamic> uploadMediaFile(File file) async {
    var request = http.MultipartRequest(
      'POST',
      Uri.parse('$baseUrl/api/upload'),
    );
    var stream = http.ByteStream(file.openRead());
    stream.cast();
    var length = await file.length();
    var multipartFileSign = http.MultipartFile(
      'files',
      stream,
      length,
      filename: file.path.split('/').last,
      contentType: MediaType('image', 'jpeg'),
    );

    request.files.add(multipartFileSign);

    var response = await request.send();
    if (response.statusCode == 200) {
      return response.stream.bytesToString();
    } else {
      throw Exception('Failed to upload media file');
    }
  }
}

```
This code will:
- Create an HTTP request to allow you upload files using `http.MultipartRequest` to the `/upload` endpoint using the POST method.
- Prepare the selected files for your upload by opening a byte stream using `http.ByteStream` from the file and specifying the length. 
- Creates a multipart file using `http.MultipartFile` from the byte stream, specifying the file name, length, content type, and field name.  
- Add the multipart file to the request. If the response status code is **200 (OK)**, it reads the response stream and converts it to a string using `response.stream.bytesToString()`, then returns the result.
    
Then in your `lib/features/screens/home_screen.dart` file, add a new method to the `_HomePageState` class to handle file uploads.
    
```js
 import 'dart:convert';
import 'dart:io';
// import 'package:file_picker/file_picker.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:strapi_media_app/features/services/strapi_service.dart';

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  List<dynamic> mediaFiles = [];
  StrapiService strapiService = StrapiService();

  @override
  void initState() {
    super.initState();
    fetchMediaFiles();
  }

  Future<void> fetchMediaFiles() async {
    try {
      final files = await strapiService.getMediaFiles();
      setState(() {
        mediaFiles = files;
      });
    } catch (e) {
      print('Error fetching media files: $e');
    }
  }

  Future<void> uploadMediaFile(context) async {
    FilePickerResult? result = await FilePicker.platform.pickFiles();
    if (result != null) {
      File file = File(result.files.single.path!);
      try {
        final response = await strapiService.uploadMediaFile(file);
        List<dynamic> jsonResponse = jsonDecode(response);
        setState(() {
          mediaFiles.add(jsonResponse[0]);
        });
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(
            content: Text('File uploaded successfully'),
          ),
        );
      } catch (e) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Error uploading media file: $e'),
          ),
        );
      }
    } else {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('No file selected'),
        ),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Strapi Media Library'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(8.0),
        child: ListView.builder(
          itemCount: mediaFiles.length,
          itemBuilder: (context, index) {
            final file = mediaFiles[index];
            final imageUrl = StrapiService.baseUrl + file['url'];
            return ListTile(
              title: Text(file['name']),
              subtitle: Text(file['mime']),
              leading: Image.network(imageUrl),
            );
          },
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => uploadMediaFile(context),
        child: const Icon(Icons.add),
      ),
    );
  }
}

```
The above code will:
- Create an `uploadMediaFile` method in the `_HomePageState` class to call the `uploadMediaFile` method from the `StrapiService` class to upload the selected files to the Strapi Media Library. 
- Uses the `FilePicker` package to allow the user to select a file from their device. If the file selected is not null, it will retrieve the selected file and prepares it for upload, else it shows a snackbar with a success or error message.

![003-image-upload.png](https://api-prod.strapi.io/uploads/003_image_upload_8905739c66.png)

## Demo
Below is a demonstration of what our application looks like.
![demofileupload-ezgif.com-optimize.gif](https://api-prod.strapi.io/uploads/demofileupload_ezgif_com_optimize_51e39fe6a7.gif)
## Conclusion
Throughout this tutorial, you've learned how to build a media gallery application using Flutter and Strapi Media Library. You have set up a Strapi project, installed the Strapi upload plugin in the project, and uploaded media data to the Strapi Media Library. You created a Flutter application to make API requests to the Media Libray to fetch, display, and upload new files.