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:
- Registered
ProtocolResolvers, in order (for example Flutter assets). resources:: the files oflib/resources/, embedded by the generator.file:: a file on disk, percent-decoded (file:my%20dir/a.txt).http:andhttps:: aUrlResource, read asynchronously.package:: the package's file through the package config (not on the web or in AOT executables).- Any other
scheme:: a resource that does not exist. - A plain path (
app.properties): aresources: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:
resources:searches the first classpath root that has the pattern's base directory;resources*:searches every root (directories added withResources.addDirectory, then the embedded files).- Wildcards follow AntPathMatcher. Matching directories are returned too.
- A
resources:pattern whose base directory does not exist gives an empty list.
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:
- The prefix is
resources:(andresources*:) instead ofclasspath:(andclasspath*:). The "classpath" islib/resources/. - Reading is asynchronous by default, because Flutter assets and URLs can only be read that way. There is no
getInputStream()orgetFile(); read the whole content. UrlResource.exists()does not send a request;existsAsync()sends aHEADrequest.- A
..that climbs above a classpath root finds nothing, and Windows paths with\work infile:patterns.