Resource

Resource is a piece of content to read, and ResourceLoader turns a location string such as resources:mail.txt into one.

Why use it

A bean that reads a file should take the location from configuration and read it, without code for each storage. The loader knows what each prefix means:

  1. Registered ProtocolResolvers, in order (for example Flutter assets).
  2. resources:: the files of lib/resources/, embedded by the generator.
  3. file:: a file on disk, percent-decoded (file:my%20dir/a.txt).
  4. http: and https:: a UrlResource, read asynchronously.
  5. package:: the package's file through the package config (not on the web or in AOT executables).
  6. Any other scheme:: a resource that does not exist.
  7. A plain path (app.properties): a resources: resource.

The application context is the loader beans get. It is also a ResourcePatternResolver, which finds every file matching a pattern such as resources:mail/*.txt.

For a fixed location, @Value('resources:...') Resource is the shortest form. For configuration values, use @Value with ${...} instead of reading files.

Example

import 'package:spring_core_dart/spring_core_dart.dart';

@Service()
class Imports {
  Imports(this.resources, @Value(r'${shop.import.file}') this.location);

  // The application context: a ResourceLoader and a ResourcePatternResolver.
  final ResourcePatternResolver resources;

  // `file:./import.csv` in production, `resources:demo.csv` in development.
  final String location;

  Future<String> read() => resources.getResource(location).readAsString();

  // Every mail template shipped in lib/resources/mail/.
  List<Resource> templates() => resources.getResources('resources:mail/*.txt');
}

Reading synchronously works only where the resource supports it:

final resource = DefaultResourceLoader().getResource('resources:banner.txt');
if (resource.exists() && resource.supportsSyncRead) {
  // Files and the embedded lib/resources/ can be read without waiting.
  final banner = resource.readAsStringSync();
}

A new prefix is added with a ProtocolResolver, which is asked before the built-in rules:

// `inline:greeting` served from memory.
final loader = DefaultResourceLoader(
  protocolResolvers: [
    FunctionProtocolResolver(
      (location, _) => location == 'inline:greeting'
          ? ByteArrayResource.fromString('hello=world')
          : null,
    ),
  ],
);

Pattern notes:

Members

Resource:

Name Type Meaning
exists() bool Whether the content is there. A UrlResource always says true; use its existsAsync() to ask the server.
isReadable bool Whether the content can be read; false for a directory.
filename String? The last part of the path, or null (bytes in memory).
description String A text for error messages, such as file [/app/config/application.properties].
uri Uri? The URI of the resource, or null.
supportsSyncRead bool Whether the ...Sync readers work. True for files and embedded resources.
readAsBytes() Future<Uint8List> Reads the whole content.
readAsString({encoding}) Future<String> Reads the whole content as text, UTF-8 unless given.
readAsBytesSync() Uint8List Synchronous read; throws UnsupportedError unless supportsSyncRead.
readAsStringSync({encoding}) String Synchronous read as text.
createRelative(relativePath) Resource The resource at a path relative to this one; not checked to exist.

Loaders and implementations:

Name Meaning
ResourceLoader.getResource(location) The resource for a location string.
ResourcePatternResolver.getResources(pattern) Every resource matching a pattern.
DefaultResourceLoader({protocolResolvers}) The standard loader; addProtocolResolver adds more resolvers.
PathMatchingResourcePatternResolver({resourceLoader, pathMatcher}) The standard pattern resolver.
ProtocolResolver, FunctionProtocolResolver, InMemoryProtocolResolver Teach a loader a new prefix.
FileSystemResource, UrlResource, ByteArrayResource, DescriptiveResource Resources on disk, at a URL, in memory, and a placeholder that does not exist.
Resources.addDirectory(directory) Makes resources: look in a directory on disk first; returns a function that removes it.

In Spring (Java)

@Service
class Imports {
  private final ResourcePatternResolver resources;

  Imports(ResourcePatternResolver resources) {
    this.resources = resources;
  }

  String read(String location) throws IOException {
    return resources.getResource(location)
        .getContentAsString(StandardCharsets.UTF_8);
  }

  Resource[] templates() throws IOException {
    return resources.getResources("classpath:mail/*.txt");
  }
}

Differences that matter:

See also