How to load resource files or package: URIs in Dart? - dart

There is a way to get the path of the currently executing script and then find the resource files relative to this location, but there is no guarantee that the directory structure is the same when the application is published.
Does Dart provide a generic way to load resources (files, data, ...) in a way that works also with pub run or pub global run?
Asked another way, how do I dynamically load the contents of a package: URI, in a Dart app, at runtime?

Update
The resource package was published.
Update
This is being reworked. The Resource class will be moved to a package.
Original
There is a new Resource class in Dart (tested with Dart VM version: 1.12.0-edge.d4d89d6f12b44514336f1af748e466607bcd1453 (Fri Aug 7 19:38:14 2015))
resource_example/lib/resource/sample.txt
Sample text file.
resource_example/bin/main.dart
main() async {
var resource = const Resource('package:resource_example/resource/sample.txt');
print(await resource.readAsString());
// or for binary resources
// print(await resource.readAsBytes());
}
Executing
dart bin/main.dart
prints
Sample text file.
Executing
pub global activate -spath .
pub global run resource_example:main
also prints
Sample text file.
It's also possible to pass the content of a variable as a resource. This might for example be convenient in unit tests to pass mock resources.
const sampleText = "Sample const text";
main() async {
var uriEncoded = sampleText.replaceAll(' ', '%20');
var resource = new Resource('data:application/dart;charset=utf-8,$uriEncoded');
print(await resource.readAsString());
}
Arbitrary file or http uris are supported as well but this might not work when using pub run or pub global run when using relative paths.
main() async {
var uri = new Uri.file('lib/resource/sample.txt');
var resource = new Resource(uri.toString());
print(await resource.readAsString());
}
For more details see
- http://blog.sethladd.com/2015/08/dynamically-load-package-contents-with.html
- https://codereview.chromium.org/1276263002/

Related

How to get the file path to an asset included in a Dart package?

I am writing a Dart package (not Flutter). I have included a few bitmap images as public assets, e.g., lib/assets/empty.png. When this package is running as a command-line app for an end-user, how can I get the file path to these assets on the user's system?
Use-case: My Dart package calls out to FFMPEG, and I need to tell FFMPEG where to find these asset files on the system that's using my package. For example, the call to FFMPEG might look like:
ffmpeg -i "path/to/lib/assets/empty.png" ...
Accessing a Dart package's assets can happen in two modalities:
Running a Dart CLI app with the dart tool and accessing a dependency's assets, or
Running an executable CLI app
The difference between these two situations is that when you're running a CLI app using the dart tool, all of your dependencies are available as structured packages in a local cache on your system. However, when you're running an executable, all relevant code is compiled into a single binary, which means you no longer have access at runtime to your dependencies' packages, you only have access to your dependencies' tree-shaken, compiled code.
Accessing assets when running with dart
The following code will resolve a package asset URI to a file system path.
final packageUri = Uri.parse('package:your_package/your/asset/path/some_file.whatever');
final future = Isolate.resolvePackageUri(packageUri);
// waitFor is strongly discouraged in general, but it is accepted as the
// only reasonable way to load package assets outside of Flutter.
// ignore: deprecated_member_use
final absoluteUri = waitFor(future, timeout: const Duration(seconds: 5));
final file = File.fromUri(absoluteUri);
if (file.existsSync()) {
return file.path;
}
This resolution code was adapted from Tim Sneath's winmd package: https://github.com/timsneath/winmd/blob/main/lib/src/metadatastore.dart#L84-L106
Accessing assets when running an executable
When compiling a client app to an executable, that client app simply cannot access any asset files that were stored with the dependent package. However, there is a work around that may work for some people (it did for me). You can store Base64 encoded versions of your assets in your Dart code, within your package.
First, encode each of your assets into a Base64 string and store those strings somewhere in your Dart code.
const myAsset = "iVBORw0KGgoAAA....kJggg==";
Then, at runtime, decode the string back to bytes, and then write those bytes to a new file on the local file system. Here's the method I used in my case:
/// Writes this asset to a new file on the host's file system.
///
/// The file is written to [destinationDirectory], or the current
/// working directory, if no destination is provided.
String inflateToLocalFile([Directory? destinationDirectory]) {
final directory = destinationDirectory ?? Directory.current;
final file = File(directory.path + Platform.pathSeparator + fileName);
file.createSync(recursive: true);
final decodedBytes = base64Decode(base64encoded);
file.writeAsBytesSync(decodedBytes);
return file.path;
}
This approach was suggested by #passsy
Have a look at the dcli package.
It has a 'pack' command designed to solve exactly this problem.
It encodes assets into dart files that can be unpacked at runtime.

static final object changes identity

I have a browser application written in Dart. I noticed a strange error appearing where my StageXL ResourceManager was missing the resources that it previously had. After debugging the program for a while I ended up with this situation:
In global.dart:
class Global {
static final ResourceManager resourceManager = new ResourceManager();
}
In main function:
var resources = Global.resourceManager;
resources.addBitmapData("Player", "images/player_base.png");
await resources.load();
print("in main: ${identityHashCode(Global.resourceManager)} = "
" ${Global.resourceManager.resources}, isolate: ${identityHashCode(
Isolate.current)}");
In another function where I need to access the resource afterwards:
print("elsewhere: ${identityHashCode(Global.resourceManager)} = "
" ${Global.resourceManager.resources}, isolate: ${identityHashCode(
Isolate.current)}");
Expected output (identityHashCodes match and so do the object contents):
in main: 12345678 = [ResourceManagerResource [kind=BitmapData, name=Player,
url = images/player_base.png]], isolate: 09876543
elsewhere: 12345678 = [ResourceManagerResource [kind=BitmapData,
name=Player, url = images/player_base.png]], isolate: 09876543
Actual output (note the identityHashCode mismatch):
in main: 516570559 = [ResourceManagerResource
[kind=BitmapData, name=Player, url = images/player_base.png]],
isolate: 843028171
elsewhere: 419835243 = [], isolate: 843028171
I thought this may have something to do with running in a different isolate (not familiar with them) but as you can see, the current isolates identityHashCodes match.
That is surprising. My best guess is that you are importing the same library twice, using different URIs. The fact that one of your files is a "main" file supports this, since its a common mistake to specify the main file on the command line as a file and have it import a package library using a relative reference.
Is your "main" file in a package lib directory, and does it import the resource file using a relative path? If so, try changing that import to a package:packageName/thepath URI instead and see if it changes anything.
(My personal recommendation is to never have a Dart library URL that contains lib, whether in an import/export or on the command line. Always use a package: URI in that case instead.)

Get current script path or current project path using new test runner

I am porting old vm unittest files using the new test package. Some relies on input files in sub directories of my test folder. Before I was using Platform.script to find the location of such files. This works fine when using
$ dart test/my_test.dart
However using
$ pub run test
this is now pointing to a temp folder (tmp/dart_test_xxxx/runInIsolate.dart). I am unable to locate my test input files anymore. I cannot rely on the current path as I might run the test from a different working directory.
Is there a way to find the location of my_test.dart (or event the project root path), from which I could derive the locations of my files?
This is a current limitation of pub run.
What I currently do when I run into such requirements is to set an environment variable and read them from within the tests.
I have them set in my OS and set them from grinder on other systems before launching tests.
This also works nice from WebStorm where launch configurations allow to specify environment variables.
This might be related http://dartbug.com/21020
I have the following workaround in the meantime. It is an ugly workaround that gets me the directory name of the current test script if I'm running it directly or with pub run test. It will definitely break if anything in the implementation changes but I needed this desperately...
library test_utils.test_script_dir;
import 'dart:io';
import 'package:path/path.dart';
// temp workaround using test package
String get testScriptDir {
String scriptFilePath = Platform.script.toFilePath();
print(scriptFilePath);
if (scriptFilePath.endsWith("runInIsolate.dart")) {
// Let's look for this line:
// import "file:///path_to_my_test/test_test.dart" as test;
String importLineBegin = 'import "file://';
String importLineEnd = '" as test;';
int importLineBeginLength = importLineBegin.length;
String scriptContent = new File.fromUri(Platform.script).readAsStringSync();
int beginIndex = scriptContent.indexOf(importLineBegin);
if (beginIndex > -1) {
int endIndex = scriptContent.indexOf(importLineEnd, beginIndex + importLineBeginLength);
if (endIndex > -1) {
scriptFilePath = scriptContent.substring(beginIndex + importLineBegin.length, endIndex);
}
}
}
return dirname(scriptFilePath);
}

How to auto run transformer on asset change

Barback package description:
An asset build system. Given a set of input files and a set of
transformations (think compilers, preprocessors and the like), will
automatically apply the appropriate transforms and generate output
files. When inputs are modified, automatically runs the transforms
that are affected. Runs transforms asynchronously and in parallel when
possible to maximize responsiveness.
Docs on assets and transformers say:
For pub serve, the transformers run when the dev server starts up and
whenever a source asset changes. The pub build command runs the
transformers once and then exits.
So I took this example:
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:barback/barback.dart';
import 'package:markdown/markdown.dart';
import 'dart:async';
class ConvertMarkdown extends Transformer {
// A constructor named "asPlugin" is required. It can be empty, but
// it must be present. It is how pub determines that you want this
// class to be publicly available as a loadable transformer plugin.
ConvertMarkdown.asPlugin();
// Any markdown file with one of the following extensions is
// converted to HTML.
String get allowedExtensions => ".md .markdown .mdown";
Future apply(Transform transform) {
return transform.primaryInput.readAsString().then((content) {
// The extension of the output is changed to ".html".
var id = transform.primaryInput.id.changeExtension(".html");
String newContent = "<html><body>"
+ markdownToHtml(content)
+ "</body></html>";
transform.addOutput(new Asset.fromString(id, newContent));
});
}
}
It runs as expected with pub build, but does nothing with pub serve except printing:
Build completed successfully
every time I change any file (not only appropriate asset) in the directory.
After reading this I think that Dart has some problems with watching files(not only directories) on Windows platform.
It is true that pub serve runs transformers after each file modification. But compared to pub build it is not outputting the results to the build/ folder. pub serve is a development server, so you need to request you assets via HTTP, for example in a browser.
See the pub serve documentation for more details

Access to pubspec.yaml attributes (version) from Dart app

Is there any way to access some of the attributes listed in a pubspec.yaml file in that files Dart application?
In particular, the version and description attributes may be quite useful to see in a version info dialog, or even a '--version' when using a console app. I haven't been able to find a way to access in the API. I'm not sure if Mirrors would have anything appropriate, but if a web app is compiled to JS, then I don't see the description anywhere in the output JS.
Thanks.
EDIT
feature request: https://code.google.com/p/dart/issues/detail?id=18769
FOR FLUTTER ONLY
Please use this new package package_info_plus from flutter community.
import 'package:package_info_plus/package_info_plus.dart';
PackageInfo packageInfo = await PackageInfo.fromPlatform();
String appName = packageInfo.appName;
String packageName = packageInfo.packageName;
String version = packageInfo.version;
String buildNumber = packageInfo.buildNumber;
BELOW SOLUTION IS DEPRICATED.
I know the OP wants to read YAML but for flutter dev's you guys can read the version and other info of the application using package_info.
This is the sample to fetch details from Android/iOS application.
import 'package:package_info/package_info.dart';
PackageInfo packageInfo = await PackageInfo.fromPlatform();
String appName = packageInfo.appName;
String packageName = packageInfo.packageName;
String version = packageInfo.version;
String buildNumber = packageInfo.buildNumber;
you can install the "dart_config" package and use this code to parse a pubspec.yaml file:
import 'package:dart_config/default_server.dart';
import 'dart:async';
void main() {
Future<Map> conf = loadConfig("../pubspec.yaml");
conf.then((Map config) {
print(config['name']);
print(config['description']);
print(config['version']);
print(config['author']);
print(config['homepage']);
print(config['dependencies']);
});
}
The output looks similar to this:
test_cli
A sample command-line application
0.0.1
Robert Hartung
URL
{dart_config: any}
EDIT
You can do it with the Yaml package itself:
*NOTE: this will not work on Flutter Web
import 'package:yaml/yaml.dart';
import 'dart:io'; // *** NOTE *** This will not work on Flutter Web
void main() {
File f = new File("../pubspec.yaml");
f.readAsString().then((String text) {
Map yaml = loadYaml(text);
print(yaml['name']);
print(yaml['description']);
print(yaml['version']);
print(yaml['author']);
print(yaml['homepage']);
print(yaml['dependencies']);
});
}
Regards Robert
None of the above answers worked for me, but here's a working solution for a Flutter app:
In your pubspec.yaml add the "pubspec.yaml" to assets:
assets:
- assets/
- pubspec.yaml
If you have a widget where you need to show the app version like this:
...
Container(
child: Text('Version: 1.0.0+1'),
),
...
Wrap your widget with a FutureBuilder like this:
import 'package:flutter/services.dart';
import 'package:yaml/yaml.dart';
...
FutureBuilder(
future: rootBundle.loadString("pubspec.yaml"),
builder: (context, snapshot) {
String version = "Unknown";
if (snapshot.hasData) {
var yaml = loadYaml(snapshot.data);
version = yaml["version"];
}
return Container(
child: Text(
'Version: $version'
),
);
}),
...
The services rootBundle property contains the resources that were packaged with the application when it was built.
If you want to show the version without the build number, you can split the string like so:
'Version: ${version.split("+")[0]}'
UPDATE: As mentioned by #wildsurfer, this approach has a potential security risk in web development because the pubspec.yaml is shared with the browser!
So assuming that this is for a dart cli application then the #Robert suggestion won't work.
dart_config isn't available for dart 2.x and your pubspec.yaml isn't going to be relative to your cwd except when you are in your development environment
So you need to get the pubspec.yaml relative to the libraries executable path.
This example uses the 'paths' package but it isn't required.
This can be obtained by:
import 'package:path/path.dart';
String pathToYaml = join(dirname(Platform.script.toFilePath()), '../pubspec.yaml');
You can now read the yaml:
import 'package:path/path.dart';
import 'package:yaml/yaml.dart';
String pathToYaml = join(dirname(Platform.script.toFilePath()), '../pubspec.yaml');
File f = new File(pathToYaml);
String yamlText = f.readAsStringSync();
Map yaml = loadYaml(yamlText);
print(yaml['name']);
print(yaml['description']);
print(yaml['version']);
print(yaml['author']);
print(yaml['homepage']);
print(yaml['dependencies']);
});
For Flutter only (Web, Android and IOS)... since October 2020
If you want your app working on Web, Android and IOS use "Package info_plus" instead.
How to Incorporate Automated Version Information into A Dart Command Line App
To update version information in your code without having to package a resource file to be parsed during run time, you can have the information hard coded into an automatically generated dart source file which gets compiled into your binary. The following example hard codes the version, name, and description information into the Map object "meta" in a meta.dart file. The meta.dart file is recreated and overwritten every time the test suite is run in development. To verify the source code has the correct version information, the app's code verifies the version and other meta information against the attributes in the pubspec.yaml file (but only when run as interpreted code in development). If there is a difference from pubspec.yaml, it throws an exception. Once compiled into a binary, it will skip that check as it won't find the pubspec.yaml file, so no error is thrown from the binary. Even if a pubspec.yaml file happens to be around and is found, it only throws an exception and does not create a "meta.dart" source file.
1. Create a MetaUpdate class and save it as "meta_update.dart":
import 'dart:io';
import 'package:yaml/yaml.dart';
import 'meta.dart';
class MetaUpdate {
String pathToYaml = "";
String metaDartFileContents = "";
MetaUpdate(this.pathToYaml);
void writeMetaDartFile(String metaDartFilePath) {
File metaDartFile = File(metaDartFilePath);
String metaDartFileContents = """
/// DO NOT EDIT THIS FILE EXCEPT TO ENTER INITIAL VERSION AND OTHER META INFO
/// THIS FILE IS AUTOMATICALLY OVER WRITTEN BY MetaUpdate
Map<String, String> meta = <String, String>{
"name": "${getPubSpec('name')}",
"description":
// ignore: lines_longer_than_80_chars
"${getPubSpec('description')}",
"version":"${getPubSpec('version')}",
};
""";
metaDartFile.writeAsStringSync(metaDartFileContents);
}
String getPubSpec(String pubSpecParam) {
File f = File(pathToYaml);
String yamlText = f.readAsStringSync();
// ignore: always_specify_types
Map yaml = loadYaml(yamlText);
return yaml[pubSpecParam];
}
void verifyLatestVersionFromPubSpec() {
try {
File f = File(pathToYaml);
//exit if no pubspec found so no warning in production
if (!f.existsSync()) return;
//compare meta.dart with pubspec meta and give warning if difference
if (meta.keys
.where((dynamic e) => (meta[e] != getPubSpec(e)))
.isNotEmpty) {
throw Exception(
"""Version number and other meta attributes in code are different from pubspec.yaml. Please check pubspec.yaml and then run test so that MetaUpdate can update meta information in code, then recompile""");
}
} on Exception {
rethrow;
}
}
}
2. Create a "meta.dart" file:
/// DO NOT EDIT THIS FILE EXCEPT TO ENTER INITIAL VERSION AND OTHER META INFO
/// THIS FILE IS AUTOMATICALLY OVER WRITTEN BY MetaUpdate
Map<String, String> meta = <String, String>{
"name": "Acme Transmogrifier",
"description":
"The best dart application ever.",
"version":"2021.09.001",
};
When you initially create the meta.dart file, copy your specific info from pubspec.yaml. This will later be overwritten each time your MetaUpdate.writeMetaDartFile() is run, changing the contents whenever the info in pubspec.yaml is changed.
3. Implement The Version Update In Your Test Code
Add the following in the first line of Main() in your test code (not the source of the main program, we don't want it to be compiled into the binary), changing the path to meta.dart as appropriate:
MetaUpdate("pubspec.yaml").writeMetaDartFile("lib/src/meta.dart");
4. Add A Meta Check To Your Main Code
Put this in your app's code so it is one of the first methods executed when your app is run - it will generate an exception if there is a difference between the attributes shown in the meta.dart and pubspec.yaml:
MetaUpdate("pubspec.yaml").verifyLatestVersionFromPubSpec();
5. Using
Make sure the Name, Version, and Description information in pubspec.yaml contains the latest information you want reflected in your code.
Import "meta.dart" and insert meta['name'], meta['version'], etc. where you need them to be shown (e.g., in --help or --version messages to be printed to the console).
As long as you run your tests before compiling your code the meta information will be accurately reflected in your code.
You can access pubspec.yaml properties with the official pubspec_parse package from the Dart team.
dart pub add pubspec_parse
import 'dart:io';
import 'package:pubspec_parse/pubspec_parse.dart';
final pubspec = File('pubspec.yaml').readAsStringSync();
final parsed = Pubspec.parse(pubspec);
You can then access typed properties on the parsed object.
You can find supported properties here: https://pub.dev/documentation/pubspec_parse/latest/pubspec_parse/Pubspec-class.html.

Resources