2023-01-17

[Flutter] Katana Storage

Hello. My name is Masaru Hirose.

Here are some of the packages that will be used for the comprehensive framework that will be released later.

This time, the package will be around file storage.

This is a testing package for later use of Cloud Storage for Firebase.

I’ve put together some instructions on how to use it, so if you’re interested, go ahead and give it a try!

katana_storage

Base package to facilitate switching between Local and Firebase storage implementations.
https://pub.devhttps://pub.dev
title

Introduction

The ability to upload files representing image files is used by a variety of applications.

Uploading image files, such as social networking and matching applications, can facilitate communication.

There are many cloud services that allow you to upload files and make them available to your application, but if you are using Flutter and Firestore, Cloud Storage for Firebase is probably the best choice for you.

This package implements a package that allows switching between Firebase and local file storage using an adapter similar to the one implemented in katana_model.

Installation

Import the following packages.

flutter pub add katana_storage

If you use Cloud Storage for Firebase, import the following packages together.

flutter pub add katana_storage_firebase

Implementation

Advance preparation

Always place the StorageAdapterScope widget near the root of the app.

Pass a StorageAdapter such as LocalStorageAdapter as the parameter adapter.

// main.dart
import 'package:flutter/material.dart';
import 'package:katana_storage/katana_storage.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return StorageAdapterScope(
      adapter: const LocalStorageAdapter(),
      child: MaterialApp(
        home: const StoragePage(),
        title: "Flutter Demo",
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
      ),
    );
  }
}

Storage Object Creation

First, create one Storage object per file.

Pass a StorageQuery describing the relative path on the remote side at the time of creation.

Since the Storage object inherits from ChangeNotifier, it can be used in conjunction with addListener and riverpod's ChangeNotifierProvider to monitor its status.

final storage = Storage(const StorageQuery("test/file"));
class StoragePage extends StatefulWidget {
  const StoragePage({super.key});

  @override
  State<StatefulWidget> createState() => StoragePageState();
}

class StoragePageState extends State<StoragePage> {
  final storage = Storage(const StorageQuery("test/file"));
  final controller = TextEditingController();

  @override
  void initState() {
    super.initState();
    storage.addListener(_handledOnUpdate);
  }

  void _handledOnUpdate() {
    setState(() {});
  }

  @override
  void dispose() {
    super.dispose();
    storage.removeListener(_handledOnUpdate);
    storage.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("App Demo")),
      body: TextField(
        controller: controller,
        expands: true,
        textAlignVertical: TextAlignVertical.top,
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () async {
          final text = controller.text;
          final bytes = Uint8List.fromList(text.codeUnits);
          await storage.uploadWithBytes(bytes);
        },
        child: const Icon(Icons.upload_file),
      ),
    );
  }
}

Uploading

The following methods are available to perform uploads

  • upload:Upload the file by specifying the file path locally.
  • uploadWithBytes:Upload directly by passing byte data.

A StorageValue is passed in the return value. The remote full path of the uploaded file is stored here.

final bytes = Uint8List.fromList(text.codeUnits);
final storageValue = await storage.uploadWithBytes(bytes);
print("Remote path is: ${storageValue.remote.path}");

Downloading

To download a file that has already been uploaded to storage, use the download method.

It is also possible to specify a relative path to a file to be saved locally as an argument.

StorageValue is passed as the return value, where the local full path of the downloaded file and the actual data of the file are stored.

Saving to local files is not supported on the Web.

final bytes = Uint8List.fromList(text.codeUnits);
final storageValue = await storage.download("file.jpg");
print("Local path is: ${storageValue.local.path}");
print("Local data is: ${storageValue.local.bytes}");

StorageAdapter

It is possible to change the storage system by passing StorageAdapterScope when defining it.

  • RuntimeStorageAdapter:Data is uploaded only in the app's memory. It is not actually saved. Please use this function during testing.
  • LocalStorageAdapter:Storage adapter for storing files locally on the terminal; note that both local and remote in download and upload are in the terminal. This is useful for implementing screen mocks.
  • FirebaseStorageAdapter:Storage adapter to use Cloud Storage for Firebase. Can be used between apps; requires initial Firebase configuration.

Conclusion

I made it for my own use, but if you think it fits your implementation philosophy, by all means, use it!

Also, I releasing the source here, so issues and PullRequests are welcome!

If you have any further work requests, please contact me directly through my Twitter or website!