
# 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 `ProtocolResolver`s, 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](../config/value.md) with `${...}` instead of reading files.

## Example

```dart
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:

```dart
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:

```dart
// `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 with `Resources.addDirectory`, then the embedded files).
- Wildcards follow [AntPathMatcher](path-matcher.md). 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)

```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:` (and `resources*:`) instead of `classpath:` (and `classpath*:`). The "classpath" is `lib/resources/`.
- Reading is asynchronous by default, because Flutter assets and URLs can only be read that way. There is no `getInputStream()` or `getFile()`; read the whole content.
- `UrlResource.exists()` does not send a request; `existsAsync()` sends a `HEAD` request.
- A `..` that climbs above a classpath root finds nothing, and Windows paths with `\` work in `file:` patterns.

## See also

- [AntPathMatcher](path-matcher.md)
- [@Value](../config/value.md)
- [@PropertySource](../config/property-source.md)
- [MessageSource](../messages/message-source.md)
- [ApplicationContext](../beans/application-context.md)
