I need to download a huge file in dart and also show progress while downloading it.
So I implemented this function:
import 'dart:io';
Future<void> download(String url, String path) async {
HttpClient client = HttpClient();
final request = await client.getUrl(Uri.parse(url));
request.close().then((HttpClientResponse response) async {
final file = File(path).openWrite(mode: FileMode.writeOnlyAppend);
await response.listen((data) {
file.add(data);
// show progress bar
...
}, onError: (e) {
throw e;
}, onDone: () {
file.close().then((e) {
print("Done");
return;
});
});
});
}
This works great with for downloading 1 file at a time.
But I want to able to call it in a loop and control the number of downloads that can occur at once.
Which brings me to this question on how to implement this using a Stream and Completer.
Related
I'm currently using a Firebase Cloud Function, which I wish I can use on Call to check if an Image has adult content, and see some of the tutorials out there I manage to create this function:
exports.checkImage = functions.https.onCall(async (data, context) => {
const img_base64 = data;
const request = {
image: {
content: img_base64
}
};
try {
const [result] = await client2.safeSearchDetection(request);
console.log(result.safeSearchAnnotation?.adult);
return result.safeSearchAnnotation?.adult;
} catch (error) {
console.log(error);
return;
}
return;
});
Now I'm tried to send the Image using an iOS Emulator with that call Function in 2 ways:
First Option I tried:
1.
exports.checkImage = functions.https.onCall(async (data, context) => {
const img_base64 = data;
const request = {
image: {
content: img_base64
}
};
try {
const [result] = await client2.safeSearchDetection(request);
console.log(result.safeSearchAnnotation?.adult);
return result.safeSearchAnnotation?.adult;
} catch (error) {
console.log(error);
return;
}
return;
});
The problem here is I get this Error on my Debugging Console:
[VERBOSE-2:ui_dart_state.cc(209)] Unhandled Exception: MissingPluginException(No implementation found for method FirebaseFunctions#call on channel plugins.flutter.io/firebase_functions)
#0 convertPlatformException (package:cloud_functions_platform_interface/src/method_channel/utils/exception.dart:16:5)
#1 MethodChannelHttpsCallable.call (package:cloud_functions_platform_interface/src/method_channel/method_channel_https_callable.dart:39:13)
<asynchronous suspension>
#2 HttpsCallable.call (package:cloud_functions/src/https_callable.dart:35:37)
<asynchronous suspension>
#3 DatabaseMethods.clearImage
package:property1/providers/database.dart:294
In someplace I read that the onCall Cloud Functions can be use as regular End Points so I did this:
clearImage(imageX) async {
final url = Uri.parse(
'https://us-central1-XXXXXXX-XXXX.cloudfunctions.net/checkImage',
);
try {
final response = await http.post(
url,
body: json.encode({
"image": {
"content": imageX
}),
);
}
}
In this one, I only get an Invalid Request unable to process.
I even try this from Postman getting the same result bad request.
As additional data I call this function from where I select the Image with this code:
void _pickImage() async {
final imagePicker = ImagePicker();
final pickedImageFile = await imagePicker.getImage(
source: ImageSource.gallery,
imageQuality: 50,
);
setState(() {
if (pickedImageFile != null) {
_pickedImage = File(pickedImageFile.path);
}
});
// This section will be used to check the Images with Google Vision.
List<int> imageBytes = _pickedImage.readAsBytesSync();
String base64Image = base64Encode(imageBytes);
DatabaseMethods().clearImage(base64Image);
widget.imagePickFn(File(pickedImageFile.path));
}
Basically, I convert the image to a Base64 encoded image that I send so Cloud Vision could work with it.
I even try to just check if I get to the Cloud Function by Commenting out the try of Cloudvision and just adding a console log to get what data is receiving but it is never reached, always an Invalid request, unable to process is what I get.
Any ideas on why I'm not reaching the Function, or on how I can test this from my iOS Emulator.
Kind Regards,
For calling onCall cloud functions from flutter apps, you have to use FirebaseFunctions.instance.httpsCallable():
import 'package:cloud_functions/cloud_functions.dart';
try {
final result =
await FirebaseFunctions.instance.httpsCallable('checkImage').call();
} on FirebaseFunctionsException catch (error) {
print(error.code);
print(error.details);
print(error.message);
}
First, install the package:
flutter pub add cloud_functions
Here's the documentation.
I have a facade function that reloads the current firebase user and returns it. The thing is that the user reloading part has a timeout and it needs to be tested.
Function:
Future<Option<User>> getSignedInUser() async {
// Reload currentUser if possible
// it mustn't throw [TimeoutException] for whole function,
// this is what this try/catch does
try {
await reloadCurrentUser().timeout(const Duration(seconds: 20));
} catch (e) {
log(e.toString(), name: TAG);
}
return optionOf(_auth.currentUser);
}
reloadCurrentUser() function:
Future<Either<AuthFailure, Unit>> reloadCurrentUser() async {
try {
await _auth.currentUser?.reload();
return right(unit);
} catch (e) {
log(e.toString(), name: TAG);
return left(const AuthFailure.userReloadingError());
}
}
The question is how to test reloadCurrentUser() timeout? I'm trying to throw a TimeoutException when this function is called, but then it throws an error for the whole test.
Current Test function:
test(
'Reaches timeout when reloading currentUser, '
'throws TimeoutException, but function continues '
'and returns optionOf currentUser', () async {
reset(fakeFirebaseAuth);
reset(fakeFacebookAuth);
reset(fakeGoogleSignIn);
final currentUser = FakeUser();
// It says that currentUser exists and *IS* authenticated
when(() => fakeFirebaseAuth.currentUser).thenReturn(currentUser);
when(() => firebaseAuthFacade.reloadCurrentUser())
.thenThrow(TimeoutException('timeout', const Duration(seconds: 20)));
final result = await firebaseAuthFacade.getSignedInUser();
expect(result, isA<Some<User>>());
});
Maybe it's better to remove timeout and use some connectivity package to ensure that the user has a network connection and only then reload the current user?
For testing I'm using mocktail package.
You can use the fake_async package.
Here's a simple example from their docs that you can modify for your use case:
import 'dart:async';
import 'package:fake_async/fake_async.dart';
import 'package:test/test.dart';
void main() {
test("Future.timeout() throws an error once the timeout is up", () {
// Any code run within [fakeAsync] is run within the context of the
// [FakeAsync] object passed to the callback.
fakeAsync((async) {
// All asynchronous features that rely on timing are automatically
// controlled by [fakeAsync].
expect(Completer().future.timeout(Duration(seconds: 5)),
throwsA(isA<TimeoutException>()));
// This will cause the timeout above to fire immediately, without waiting
// 5 seconds of real time.
async.elapse(Duration(seconds: 5));
});
});
}
How do I convert a file to Readable stream ?
I am trying to use deno's fetch api to do this, which requires a readable stream as body to put something on server.
I am not able to figure out how to convert a file to ReadableStream ?
There isn't a built-in way yet to convert a Reader to a ReadableStream.
You can convert it using the following code:
const file = await Deno.open("./some-file.txt", { read: true });
const stream = new ReadableStream({
async pull(controller) {
try {
const b = new Uint8Array(1024 * 32);
const result = await file.read(b);
if (result === null) {
controller.close();
return file.close();
}
controller.enqueue(b.subarray(0, result));
} catch (e) {
controller.error(e);
controller.close();
file.close();
}
},
cancel() {
// When reader.cancel() is called
file.close();
},
});
// ReadableStream implements asyncIterator
for await (const chunk of stream) {
console.log(chunk);
}
Have in mind that Deno (1.0.5) fetch does not yet support a ReadableStream as request body.
So currently to post a file, you'll need to buffer the contents.
const body = await Deno.readAll(file);
await fetch('http://example.com', { method: 'POST', body });
In my StatefulWidget in initState i have a method:
#override
void initState() {
super.initState();
getMyChannels();
}
getMyChannels method run a http method to get data from service and store data into database:
void getMyChannels() async {
// get data from servise and store them
_myChannel = await MyToolsProvider()
.getChannelMe("df6b88b6-f47d****");
getToolsRelToChannels(); // get data from database
setState(() {});
}
As you can see i have getToolsRelToChannels method. This method fetch data from local database. This data must be stored by await MyToolsProvider()
.getChannelMe("df6b88b6-f47d****"); method into database.
This is .getChannelMe method:
Future<ProgramsByActiveToolsModel> getChannelMe(String auth) async {
Map<String, dynamic> header = {
'Content-Type': "application/json",
"Authorization": 'Bearer $auth'
};
try {
var result = await NetworkCLient()
.getRequest(url: '$URL/api/me', header: header);
if (result != null) {
var programsByActiveToolsModel =
ProgramsByActiveToolsModel.fromJson(result);
if (programsByActiveToolsModel.responseCode == 200) {
programsByActiveToolsModel.data.forEach((item) async {
await DBProvider.db.addMyTools(item);
saveToolsbyChannelId(header, item.id);
});
return programsByActiveToolsModel;
} else
return null;
}
} catch (e) {
throw e;
}
}
In addMyTools method i store each data in one table of my database and i call saveToolsbyChannelId method for each item. This is main data that I need too.
Future<void> saveToolsbyChannelId(Map header, int channelId) async {
header["Authorization"] = 'Bearer 92122926-****';
try {
var count = await DBProvider.db.getCountToolsbyChannelId(channelId);
if (count == 0) {
var result = await NetworkCLient().getRequest(
url: '$URL/api/channel/$channelId', header: header);
if (result != null) {
var logToolsRunModel = LogTools.LogToolsRunModel.fromJson(result);
if (logToolsRunModel.responseCode == 200) {
logToolsRunModel.data.forEach((item) {
DBProvider.db.addTools(item);
});
}
}
}
} catch (e) {
throw e;
}
}
After fetching data from my service i sore these data into sqlite database .Now it's await MyToolsProvider().getChannelMe job is done!
It's time to explain getToolsRelToChannels();:
void getToolsRelToChannels() async {
_toolsRun =
await MyToolsProvider().getToolsRelatedMyChannel(_selectedChannel);
setState(() {});
}
getToolsRelatedMyChannel this method must be wait till all data in this method DBProvider.db.addTools(item) added into database and after inserting my widget must be recreated.
Future<List<ToolsByChannelIdDbModel>> getToolsRelatedMyChannel(
int channelId) async {
List<ToolsByChannelIdDbModel> list = List<ToolsByChannelIdDbModel>();
try {
var result = await DBProvider.db.getToolsById(channelId);
result.forEach((item) {
list.add(ToolsByChannelIdDbModel.fromJson(item));
});
return list;
} catch (e) {
print(e);
}
}
}
but my code is wrong because after running await MyToolsProvider().getChannelMe(***) getToolsRelToChannels method is executed and nothing is stored into database to fetching yet!!!
How could i notify my main widget after finishing database inserting???
I can not to use FutureBuilder because when run for first time, my database is empty !!!
You should await saveToolsbyChannelId in getChannelMe and await DBProvider.db.addTools(item); in saveToolsbyChannelId, otherwise you are trying to read from the database before the data has been written to it. This is assuming the rest of your code is correct, which we cannot tell for sure because there are lots of variables such as _selectedChannel that we know nothing about.
UPDATED - Check below.
What you want is to await ALL async operations. In your case
#override
void initState() async {
super.initState();
await getMyChannels();
}
and
await saveToolsbyChannelId(header, item.id);
and if DBProvider.db.addTools is asynchronous, then
await DBProvider.db.addTools(item);
UPDATE:
Since its not possible to make initState() async, you can use a callback in the future:
#override
void initState() {
super.initState();
var channelsFuture = getMyChannels();
channelsFuture.then((resp){
setState(() {});
});
}
I'd suggest that you reconsider your whole approach (from a clean architecture point of view). Take a look at the BLoC pattern. It basically boils down to this :
There's another class (called the BLoC) which handles the business logic (getting data from network and adding to the database). The widget class only handles the UI.
You kick off your asynchronous processing in the BLoC class from the widget's initState(). There's a stream listener in the widget that listens to the completion of the task by BLoC.
As soon as the BLoC completes the async task, it notifies the widget by using a stream. The stream listener in the widget knows that the data is ready and it updates the UI by calling setState();
Done !!
I am implementing a command/response pattern where the user writes to a command collection by calling add with a payload under his own userId, and then gets the data from a similar response path.
However the code below doesn't work, because onSnapshot can not listen for a document that hasn't yet been created (document command.id in the /responses/{userId}/register collection). This would be easy to solve with an onCreate handler, which exists for cloud functions but not for the JS firebase client API it seems.
This is using redux-firestore and some of my app helper functions, but you'll get the idea. The command and response document structures use { payload, error} similar to FSA
Cloud Function
export const register = functions.firestore
.document("commands/{userId}/register/{commandId}")
.onCreate(async event => {
const payload = event.data.get("payload");
const { userId, commandId } = event.params;
const response = db.document(`responses/${userId}/register/${commandId}`)
// possibly something bad will happen
try {
// do something with payload...
return response.set({
payload: "ok" // or pass relevant response data
})
} catch(err) {
return response.set({
error: true
payload: error
})
}
});
Client
export async function register(fs: any, userId: string) {
try {
// issue a new command
const command = await fs.add(
{ collection: `commands/${userId}/register` },
{ payload: fs.firestore.FieldValue.serverTimestamp() }
);
// wait for the response to be created
fs.onSnapshot(
{ collection: `responses/${userId}/register`, doc: command.id },
function onNext(doc) {
const {error, payload} = doc.data()
if (error) {
return notify.error({ title: 'Failed to register', message: payload.message });
}
notify.json(payload);
},
function onError(err) {
notify.error(err);
}
);
} catch (err) {
notify.error(err);
}
}
Is there no such thing as onCreate for web clients?
The only scalable solution I can think of is to store the response data as a child in the command document, but I think it is not as nice, because I suspect you can not make the permissions as strict then.
I would like the user only to be able to write to the command, and only read from the response paths. If I place the response as a child of command, this would not be possible I think?
I'm wondering if I'm not overlooking some API...