The following example (1) reads a file and prints the contents without explicitly assigning the file contents to a variable (ie. “.then(stdout.write)”). However, if I want to do more than just print the contents (2), I need to assign the contents to a variable (I think).
Is it possible to achieve that (print the contents and do more), without assigning the text of the file to a variable?
In the first example, is an implicit variable created? Or, put another way, does example1 use less resources by not creating an explicit variable?
//Example 1:
import 'dart:io';
void main() {
new File(new Options().script)
.readAsString(encoding: Encoding.ASCII)
.then(stdout.write)
.catchError((oError) => print(oError));
print("Reading file ...\n");
}
//Example 2:
import 'dart:io';
void main() {
new File(new Options().script)
.readAsString(encoding: Encoding.ASCII)
.then((String sText) {
stdout.write(sText+"\n\n");
print ('Completed');
})
.catchError((oError) => print(oError));
print("Reading file ...\n");
}
In the first example, this:
.then(stdout.write)
is equivalent to this:
.then((String sText) {
stdout.write(sText);
})
Technically there's one more function call, and you have one more variable, which should cost you a few bytes (I'm not sure on the exact implementation). Strings are immutable; you are only receiving a reference to the String, so you are not saving resources (other than the function call and a few bytes of memory) by using second version.
Whatever it is you want to do with the contents of the String probably will involve using resources, of course, but that shouldn't be an issue unless the file is huge.
Related
I want to read the contents of a file piece by piece through an interface (instead of reading the whole file at once with readAsBytes()). openRead() seems to do the trick, but it returns a List<int> type. And I expect it to be Uint8List, because I want to do block operations on some of the contents.
If you convert the returned List<int> to Uint8List, it seems to make a copy of the contents, which is a big loss in efficiency.
Is this how it was designed?
Historically Dart used List<int> for sequences of bytes before a more specific Uint8List class was added. A Uint8List is a subtype of List<int>, and in most cases where a Dart SDK function returns a List<int> for a list of bytes, it's actually a Uint8List object. You therefore usually can just cast the result:
var file = File('/path/to/some/file');
var stream = file.openRead();
await for (var chunk in stream) {
var bytes = chunk as Uint8List;
}
If you are uncomfortable relying on the cast, you can create a helper function that falls back to creating a copy if and only if necessary.
There have been efforts to change the Dart SDK function signatures to use Uint8List types explicitly, and that has happened in some cases (e.g. File.readAsBytes). Such changes would be breaking API changes, so they cannot be done lightly. I don't know why File.openRead was not changed, but it's quite likely that the amount of breakage was deemed to be not worth the effort. (At a minimum, the SDK documentation should be updated to indicate whether it is guaranteed to return a Uint8List object. Also see https://github.com/dart-lang/sdk/issues/39947)
Alternatively, instead of using File.openRead, you could use File.open and then use RandomAccessFile.read, which is declared to return a Uint8List.
Quick question, I create this File object, write content to disk using it, and then return it from inside an async function like so:
...
File file = await makeFileObject(filename);
return await file.writeAsString(content);
// fyi writeAsString returns Future<File>
but I then I refactored it using the .. notation, and it says it doesn't need the await after return
That's weird. Does .. await the write?
Am I returning the File object before the write occurs or the File object returned by writeAsString?
...
return (await makeFileObject(filename))
..writeAsString(content);
Is the refactor functionally equivalent to the original?
No, the refactor is not equivalent to the original. x..y evaluates to x and discards whatever y evaluates to. In your case, that means that you discard the Future returned by writeAsString, and it therefore would not be awaited. If you want your function to return only after writeAsString completes, you cannot use it with ...
If you want to avoid having the file variable, you would need to do:
return await (await makeFileObject(filename)).writeAsString(content);
However, I think that the original is more readable, and I would stick with that.
I'm trying to load multiple files, in a certain order, inside of Dart. The files are of a modified GTFS (General Transit Feed Specification) type and are such interconnected through 'ids' between the files. Because of this I'm trying to load each file one by one but due to their large size I am using the openRead method of the File class to stream them in line-by-line using the LineSplitter transformer.
How they are being loaded in (start is the byte offset of the openRead method)
Stream<List<int>> getFileStream(String fileName, {int start}) => File(fileName).openRead(start);
// Example Stream
Stream<List<int>> stops = getFileStream("stops.txt", start: 117);
Stream<List<int>> routes = getFileStream("routes.txt", start: 131);
// How the stream data is being read
stops
.transform(utf8.decoder)
.transform(LineSplitter())
.listen(
(String line) {
List<String> values = line.split(',');
// Do stuff..
},
onDone: () { },
onError: (e) { print(e.toString()); }
);
routes
.transform(utf8.decoder)
.transform(LineSplitter())
.listen(
(String line) {
List<String> values = line.split(',');
// Do stuff..
// Error occurs here as well since the processing taking place here depends on the processing which takes place when the stops file is read
},
onDone: () { },
onError: (e) { print(e.toString()); }
);
However since I'm loading in multiple files within the same function and they are all streams with a listen callback set and they depend on the processing of the files that came before them to be COMPLETELY finished the program is producing errors since all the files are being read at once line by line and the processing of the other files has not finished.
Ideally, I would like to use an await for (String line in stops) line or something similar, however that produces the following error which I do not know how to solve:
The type 'Stream<List<int>>' used in the 'for' loop must implement Stream with a type argument that can be assigned to 'String'.
This error still shows up even if I do the .transform calls on the stream before the await for line.
I've also tried chaining together the onDone methods of the streams which produced an abomination of code which still didn't work (Lists that were created and added to within the function were empty upon returning???)
It would be nice to use the await for syntax as that produces cleaner code, however I do not want to pollute the whole function tree with async, await functions especially the main() function.
The only thing that worked was using the Completer class from dart:async however the onDone methods needed to be chained for this and the code was barely readable.
Any help as well as some guidance on Futures and async/await would be appreciated.
Have you tried something like this?
await for (var line in stops
.transform(utf8.decoder)
.transform(LineSplitter())) {
List<String> values = line.split(',');
// Do stuff..
// Error occurs here as well since the processing taking place here depends on the processing which takes place when the stops file is read
}
Being new to Dart/Flutter I am using this snippet to try and load a config.json file that I have stored in my assets folder. In trying to read this file, I am using models on the Dart language Futures documentation and in the Flutter docs on reading local text files:
import 'dart:async' show Future;
import 'package:flutter/services.dart' show rootBundle;
import 'dart:convert';
Future<List> loadAsset() async {
String raw = await rootBundle.loadString('assets/config.json');
List configData = json.decode(raw);
return configData;
}
Then, inside my class, I try to load the config into a List, like this:
Future<List> configData = loadAsset();
print(configData.toString());
// prints out: Instance of 'Future<List<dynamic>>'
The result of all this seems to work. Yet I can find no way of using the data I have loaded. Any effort to access elements in the List, e.g. configData[0] results in an error:
The following _CompileTimeError was thrown building
HomePage(dirty, state: HomePageState#b1af8):
'package:myapp/pages/home_page.dart': error:
line 64 pos 19: lib/pages/home_page.dart:64:19:
Error: The method '[]' isn't defined for the class
'dart.async::Future<dart.core::List<dynamic>>'.
Try correcting the name to the name of an existing method,
or defining a method named '[]'.
I would like to convert the configData Future into a normal object that I can read and pass around my app. I am able to do something very similar, and to get it to work inside a widget's build method, using a FutureBuilder and the DefaultAssetBundle thus...
DefaultAssetBundle
.of(context)
.loadString('assets/config.json')
...but I don't want the overhead of reloading the data inside all the widgets that need it. I would like to load inside a separate Dart package and have it available as a global configuration across all my app. Any pointers would be appreciated.
I have tried the suggestion by Rémi Rousselet:
List configData = await loadAsset();
print(configData[0]);
In this case, I get a compiler error:
compiler message: lib/pages/home_page.dart:55:21: Error: Getter not found: 'await'.
compiler message: List configData = await loadAsset();
compiler message: ^^^^^
You can't do configData[0] as configData is not a List but a Future.
Instead, await the future to have access to the List inside
List configData = await loadAsset();
print(configData[0]);
You can only use await INSIDE async methods.
If you want to you your assets in entire application you want to load the asset in the main method similar like this.
void main() async {
StorageUtils.localStorage = await SharedPreferences.getInstance();
}
Now you can use localStorage synchronously in entire application and you don't need to deal with another asynchronous calls or load it again.
Different example, same principle.
I am trying to use resumable.js from Dart with this code:
var rs = new JS.JsObject(JS.context['Resumable'], [new JS.JsObject.jsify({
'target':context.server+'/upload'
})]);
files.forEach((file) {
rs.callMethod("addFile",[file]);
});
files variable is defined as List<File> (dart.html.File). When I check properties of rs object with these two lines:
JS.context["console"].callMethod("log",[rs['opts']]);
JS.context["console"].callMethod("log",[rs['files']]);
I find that rs.opts are initialized correctly, but rs.files always contains instances of ResumableFile with length 1. I guess that it is because method addFile expects parameter to be instance of Javascript File but it gets instanceof dart:html.File.
Do you have any idea how to convert dart:html.File to Javascript File or how to pass argument to resumable.js?
I know that I can alternatively use methods assignBrowse and assignDrop, but I would like to stick to my solution.
You have to use JsObject.fromBrowserObject to get the underlying js object.
files.forEach((file) {
rs.callMethod("addFile",[new JsObject.fromBrowserObject(file)]);
});