Where should I put completer.complete() in this series of Future functions? - dart

My function have to create a directory, and copy the entire folder hierarchy from another directory to this new one. All of the operation are done asynchronously, but I want this function to return a Future that when I call the .then(result) on it, will have done all of the asynchronously work.
But I don't know where exactly I should put my completer.complete() to achieve that.
Future<Directory> createCopyDirectory(Directory directoryToCreate){
Completer<Directory> completer = new Completer<Directory>();
completer.complete(
directoryToCreate.create().then((directory){
Directory contentToCopy = new Directory(globalPathOfDirectoryToCopy);
List<Future> creatingContent = new List<Future>();
contentToCopy.list(recursive:true, followLinks:false).forEach((f){
if (f is File){
File fileToCreate = new File(f.path.replaceFirst('pages', userID));
creatingContent.add(fileToCreate.create(recursive:true).then((_){
f.readAsString().then((fileContent){
fileToCreate.writeAsString(fileContent);
});
}));
}
});
return Future.wait(creatingContent).then((_){ return directoryToCreate;});
})
);
return completer.future;
}
I precise that my function work like expected, But if I try to access directly the content I should have created in this function, like in the then() call, Dart bring me an expection like I have not created the content. So the completer.complete() is surely badly placed and call then() before the content has been created.
I have tried with the completer.complete() on the ending Future.wait(creatingContent) or by replacing return directoryToCreate by completer.complete(directoryToCreate) but the result is the same.
I am a bit confused on the way to build a proper Future based function in this kind of situation.

You shouldn't need a Completer here.
Future<Directory> createCopyDirectory(Directory directoryToCreate) {
return directoryToCreate.create().then((directory) {
String userID = split(userDirectory.path).last;
Directory contentToCopy = new Directory(globalPathOfDirectoryToCopy);
List<Future> creatingContent = new List<Future>();
return contentToCopy
.list(recursive: true, followLinks: false)
.forEach((File f) {
if (f is File) {
File fileToCreate = new File(f.path.replaceFirst('pages', userID));
creatingContent.add(fileToCreate.create(recursive: true).then((_) {
return f.readAsString().then((fileContent) {
return fileToCreate.writeAsString(fileContent);
});
}));
}
}).then((_) {
return Future.wait(creatingContent).then((_) {
return directoryToCreate;
});
});
});
}
Just to demonstrate how you could use the Completer:
Future<Directory> createCopyDirectory(Directory directoryToCreate) {
Completer<Directory> completer = new Completer<Directory>();
directoryToCreate.create().then((directory) {
String userID = split(userDirectory.path).last;
Directory contentToCopy = new Directory(globalPathOfDirectoryToCopy);
List<Future> creatingContent = new List<Future>();
contentToCopy.list(recursive: true, followLinks: false).forEach((f) {
if (f is File) {
File fileToCreate = new File(f.path.replaceFirst('pages', userID));
creatingContent.add(fileToCreate.create(recursive: true).then((_) {
return f.readAsString().then((fileContent) {
return fileToCreate.writeAsString(fileContent);
});
}));
}
}).then((_) => Future
.wait(creatingContent)
.then((_) => completer.complete(directoryToCreate)));
});
return completer.future;
}

Related

Mapping a Stream<List> to another type is returning a Stream<Null>

I'm trying to transform a Stream of a list of one type into a Stream of a list of another type, and having an issue with this.
I have this list of Habits that I'm streaming from Firebase, and I want to accept that stream in a function, and return a new stream that is a list of ViewModels of another type from it. But my function is returning a stream of the wrong type.
Here is my code:
Stream<List<HabitCompletionViewModel>> _getTodaysHabits(
Stream<List<Habit>> habitsStream) {
var result = habitsStream.map((habitsList) {
habitsList.map(
(habit) async {
await _getHabitCompletionsCurrent(habit);
HabitCompletion completion = habit.completions!.firstWhere(
(completion) => completion.date
.dayEqualityCheck(DateTime.now().startOfDate()));
return HabitCompletionViewModel(completion: completion, habit: habit);
},
).toList();
});
return result;
}
I am getting a compile error because the result variable is showing as type Stream<Null> when I hover over it, where I would expect it to be Stream<List<HabitCompletionViewModel>>. Any idea what I'm doing wrong?
Your outer .map call does not have a return statement which is why you are getting a Stream<Null>.
So add a return statement like so:
Stream<List<HabitCompletionViewModel>> _getTodaysHabits(
Stream<List<Habit>> habitsStream) {
var result = habitsStream.map((habitsList) {
// added return statement here
return habitsList.map(
(habit) async {
await _getHabitCompletionsCurrent(habit);
HabitCompletion completion = habit.completions!.firstWhere(
(completion) =>
completion.date.dayEqualityCheck(DateTime.now().startOfDate()));
return HabitCompletionViewModel(completion: completion, habit: habit);
},
).toList();
});
return result;
}
However the above code still has an error because it is now returning a Stream<List<Future<HabitCompletionViewModel>>> instead of the desired Stream<List<HabitCompletionViewModel>>. To solve this you can use .asyncMap instead of .map.
Stream<List<HabitCompletionViewModel>> _getTodaysHabits(
Stream<List<Habit>> habitsStream) {
var result = habitsStream.asyncMap((habitsList) {
return Stream.fromIterable(habitsList).asyncMap(
(habit) async {
await _getHabitCompletionsCurrent(habit);
HabitCompletion completion = habit.completions!.firstWhere(
(completion) =>
completion.date.dayEqualityCheck(DateTime.now().startOfDate()));
return HabitCompletionViewModel(completion: completion, habit: habit);
},
).toList();
});
return result;
}

Url rewrite in exception middleware

I'm trying to do some custom exception handling and in there I need to redirect.
They way to go should be like this
app.UseExceptionHandler(appBuilder =>
{
appBuilder.Use(async (ctx, next) =>
{
ctx.Request.Path = "/Error";
await next();
});
});
But nothing happens, just a blank 500 page.
Any ideas except the use of
ctx.Response.Redirect("/Error")
When you use the overload of UseExceptionHandler accepting an Action<IApplicationBuilder>, you need to configure a full pipeline for the sub-builder inside to re-execute the pipeline on the exception branch. In your code, you just set the Request.Path but it is then not handled by any code. You need to add an MVC middleware as the terminal at the end. You can configure it just like you configure your main pipeline (UseMvc, UseEndpoints). In this case the terminal just need to work with the specific request path you set before. Here's the code:
app.UseExceptionHandler(appBuilder => {
appBuilder.Use(async (ctx, next) => {
ctx.Request.Path = "/Error";
await next();
});
//add the terminal to handle the branched request
appBuilder.UseMvc();
});
Now when you run into some unhandled exception, it should work just like app.UseExceptionHandler("/Error").
According to your description, I suggest you could firstly try to understand how the exceptionhanlder works.
According to the source codes, you could find if you want to write the custom middleware ,you should use the second function like this:
public static IApplicationBuilder UseExceptionHandler(this IApplicationBuilder app, Action<IApplicationBuilder> configure)
{
if (app == null)
{
throw new ArgumentNullException(nameof(app));
}
if (configure == null)
{
throw new ArgumentNullException(nameof(configure));
}
var subAppBuilder = app.New();
configure(subAppBuilder);
var exceptionHandlerPipeline = subAppBuilder.Build();
return app.UseExceptionHandler(new ExceptionHandlerOptions
{
ExceptionHandler = exceptionHandlerPipeline
});
}
If you use this function, you could find it will still call the ExceptionHandlerMiddleware with its new exceptionHandlerPipeline. And in the ExceptionHandlerMiddleware , you could find it will call this request delaget.
So if you don't catch the exception and directly set the context path it will not work. So I suggest you could try to use below codes:
app.UseExceptionHandler(appBuilder =>
{
app.Use(async (context, next) =>
{
try
{
await next();
}
catch (Exception ex)
{
ClearHttpContext(context);
var exceptionHandlerFeature = new ExceptionHandlerFeature()
{
Error = ex,
Path = "/Home/Index",
};
context.Request.Path = "/Home/Error";
context.Features.Set<IExceptionHandlerFeature>(exceptionHandlerFeature);
context.Features.Set<IExceptionHandlerPathFeature>(exceptionHandlerFeature);
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
context.Response.OnStarting(ClearCacheHeaders, context.Response);
await next();
}
});
});
ClearCacheHeaders method:
private static Task ClearCacheHeaders(object state)
{
var headers = ((HttpResponse)state).Headers;
headers[HeaderNames.CacheControl] = "no-cache,no-store";
headers[HeaderNames.Pragma] = "no-cache";
headers[HeaderNames.Expires] = "-1";
headers.Remove(HeaderNames.ETag);
return Task.CompletedTask;
}

return List of Strings from Future / Stream

My Problem is very similar to the one mentioned here and here, but for some reason these are not working for me.
Basically, I want to do some simple I/O operations (on a mobile), returning of list of strings (folder path) that contain a certain file format (let's assume for the sake of argument that I want to find all mp3 files).
This is the code I have
Future<List<String>> getFolders() async {
List<String> _dirs = new List();
await SimplePermissions.requestPermission(Permission.ReadExternalStorage);
_dirs = await findAllFolders();
return _dirs;
}
Future<List<String>> findAllFolders() async {
Directory _root = Directory("/sdcard");
bool _notInList = true;
List<String> _dirs = new List();
_root.list(recursive: true, followLinks: false)
.listen((FileSystemEntity entity) {
if(entity.toString().contains("mp3")) {
if(_dirs.length==0) {
_dirs.add(entity.parent.path.toString());
} else {
_notInList = true;
for (var i = 0; i < _dirs.length; ++i) {
if(_dirs[i] == entity.parent.path.toString()) {
_notInList = false;
}
}
if(_notInList) {
_dirs.add(entity.parent.path.toString());
}
}
}
});
return _dirs;
}
where I want to use _dirs outside of getFolders().
I know that findAllFolders() returns _dirs immediately, before my listen() event has finished (and so its length is always 0, although the actual method works fine, i.e. if I put print statements where I have _dirs.add() I can see that the correct directories are added, _dirs contains what I want but I have no idea how to return the finished _dirs list). I tried to do something in a similar way to the above mentioned post, where a Completer is used (to which I am getting an error message "Bad State: Future already completed"). The respective code would be
Future<List<String>> findAllFolders() async {
Directory _root = Directory("/sdcard");
bool _notInList = true;
List<String> _dirs = new List();
Completer<List<String>> _completer = new Completer<List<String>>();
_root.list(recursive: true, followLinks: false)
.listen((FileSystemEntity entity) {
if(entity.toString().contains("mp3")) {
if(_dirs.length==0) {
_dirs.add(entity.parent.path.toString());
} else {
_notInList = true;
for (var i = 0; i < _dirs.length; ++i) {
if(_dirs[i] == entity.parent.path.toString()) {
_notInList = false;
}
}
if(_notInList) {
_dirs.add(entity.parent.path.toString());
}
}
}
_completer.complete(_dirs);
});
return _completer.future;
}
The getFolders() function remains the same in this case. Could anyone point out where my logic is going wrong?
You're setting a listener, then immediately returning before any results are received - that's why your return is always empty. The body of findAllFolders() needs to wait for a response before returning. Try the below to replace _root.list().listen():
List<FileSystemEntity> files = await _root.list(recursive: true, followLinks: false).toList();
for (FileSystemEntity entity in files) {
// Do your filename logic and populate _dirs

breeze observableArray binding - are properties observable?

I have a viewmodel which consists of a list(foreach loop) of DoctorPrices and when clicking on an item in the list it open up a CRUD form on the side. However when i update the values on the CRUD the observableArray that is bound to the foreach is not refreshing? (although the values are updates in the DB correctly)
From my data access module i call the following query.
function getDoctorServices(doctorId) {
var query = breeze.EntityQuery
.from('DoctorPrices')
.where('DoctorID', 'eq', doctorId).orderBy('ListOrder');
return manager.executeQueryLocally(query);
}
In my viewmodel i have the following code:
this.services = ko.computed(function() {
return doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID());
});
services is bound using a foreach loop (not posting here as the code is simple and works)
When i click on a one of the DoctorPrices it gets the data as follows and places it in an observable:
this.selectedPrice = function (data, event) {
self.currentService(data);
self.showEdit(true);
};
I then bind selectPrice to a simple form that has the properties on it to be modified by the user. I then call manager.SaveChanges().
This results in the following problem: the value is being updated correctly but the GUI / Original List that is bound in the foreach is not being updated? Are the properties in breeze not observables? What is the best way to work with something like this.
I thought of a workaround and changing the code with something like this:
doctorList.viewModel.instance.currentDoctorID.subscribe(function() {
self.services([]);
self.services(doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID()));
});
But i feel that clearing the array in that way is sloppy and not the right way of doing things specially with long lists.
Can someone please point me in the right direction on how to bind observableArray properties properly so they are updated?
Additional code my VM Component:
function services() {
var self = this;
this.showForm = ko.observable(false);
this.currentService = ko.observable();
this.services = ko.observableArray(doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID()));
this.title = ko.observable();
doctorList.viewModel.instance.currentDoctorID.subscribe(function() {
self.services([]);
self.services(doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID()));
self.showDetails(false);
});
this.show = function (value) {
self.showForm(value);
};
this.showDetails = ko.observable(false);
this.addNewService = function() {
self.currentService(doctorServices.createService(doctorList.viewModel.instance.currentDoctorID()));
console.log(self.currentService().entityAspect.entityState);
self.showDetails(true);
};
this.showDelete = ko.computed(function() {
if (self.currentService() == null)
return false;
else if (self.currentService().entityAspect.entityState.isDetached()) {
self.title('Add new service');
return false;
} else {
self.title('Edit service');
return true;
}
});
this.deleteService = function() {
self.currentService().entityAspect.setDeleted();
doctorServices.saveChanges();
doctorList.viewModel.instance.currentDoctorID.notifySubscribers();
};
this.closeDetails = function () {
doctorServices.manager.rejectChanges();
doctorList.viewModel.instance.currentDoctorID.notifySubscribers();
self.showDetails(false);
};
this.selectService = function (data, event) {
self.currentService(data);
self.showDetails(true);
};
this.saveChanges = function () {
console.log(self.currentService().entityAspect.entityState);
if (self.currentService().entityAspect.entityState.isDetached()) {
doctorServices.attachEntity(self.currentService());
}
console.log(self.currentService().entityAspect.entityState);
doctorServices.saveChanges();
doctorList.viewModel.instance.currentDoctorID.notifySubscribers();
self.currentService.notifySubscribers();
self.showDetails(true);
};
}
return {
viewModel: {
instance: new services()
},
template: servicesTemplate,
};
Below is my Breeze Data Class:
define('data/doctorServices', ['jquery', 'data/dataManager', 'knockout','mod/medappBase', 'breeze', 'breeze.savequeuing'], function ($, manager, ko,base, breeze, savequeuing) {
var services = ko.observableArray([]);
return {
attachEntity:attachEntity,
getServices: getServices,
services: services,
manager:manager,
getDoctorServices: getDoctorServices,
getServiceById: getServiceById,
createService:createService,
hasChanges: hasChanges,
saveChanges: saveChanges
};
function getServices() {
var query = breeze.EntityQuery.from("DoctorPrices");
return manager.executeQuery(query).then(function (data) {
services(data.results);
}).fail(function (data) {
console.log('fetch failed...');
console.log(data);
});;
}
function getDoctorServices(doctorId) {
var query = breeze.EntityQuery
.from('DoctorPrices')
.where('DoctorID', 'eq', doctorId).orderBy('ListOrder');
var set = manager.executeQueryLocally(query);
return set;
}
function getServiceById(serviceId) {
return manager.createEntity('DoctorPrice', serviceId);
//return manager.getEntityByKey('DoctorPrice', serviceId);
}
function handleSaveValidationError(error) {
var message = "Not saved due to validation error";
try { // fish out the first error
var firstErr = error.innerError.entityErrors[0];
message += ": " + firstErr.errorMessage;
base.addNotify('error', 'Could not save.', message);
} catch (e) { /* eat it for now */ }
return message;
}
function hasChanges() {
return manager.hasChanges();
}
function attachEntity(entity) {
manager.addEntity(entity);
}
function createService(doctorId) {
return manager.createEntity('DoctorPrice', { DoctorPricingID: breeze.core.getUuid(), DoctorID:doctorId }, breeze.EntityState.Detached);
};
function saveChanges() {
return manager.saveChanges()
.then(saveSucceeded)
.fail(saveFailed);
function saveSucceeded(saveResult) {
base.addNotify('success', 'Saved.', 'Your updates have been saved.');
}
function saveFailed(error) {
var reason = error.message;
var detail = error.detail;
if (error.innerError.entityErrors) {
reason = handleSaveValidationError(error);
} else if (detail && detail.ExceptionType &&
detail.ExceptionType.indexOf('OptimisticConcurrencyException') !== -1) {
// Concurrency error
reason =
"Another user, perhaps the server, " +
"may have deleted one or all of the settings." +
" You may have to restart the app.";
} else {
reason = "Failed to save changes: " + reason +
" You may have to restart the app.";
}
console.log(error);
console.log(reason);
}
}
});
Please note this is my frist attempt at both a data class and VM. At the moment i am relying heavily on clearing the array ([]) and using notifySubscribers to make the array refresh :(
I bet you're missing an observable somewhere. I can't tell because you keep hopping from property to property whose definition is not shown.
For example, I don't know how you defined this.currentService.
I'm confused by this:
this.services = ko.computed(function() {
return doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID());
});
Why is it a ko.computed? Why not just make it an observable array.
self.service = ko.observableArray();
// ... later replace the inner array in one step ...
self.service(doctorServices.getDoctorServices(
doctorList.viewModel.instance.currentDoctorID()));
I urge you to follow the observability trail, confident that your Breeze entity properties are indeed observable.
vm.selectedPrice = ko.dependentObservable(function () {
return doctorServices.getDoctorServices(doctorList.viewModel.instance.currentDoctorID());
}, vm);
vm is ur model on which u applied bindings , try this it will work.

retrieve multiple stores in dart

I use boilerplate code to retrieve data from 1 store such as
MonthStore monthStore = new MonthStore();
monthStore.open().then((months) {
but I am having difficulty retrieving data from multiple, related stores. Is this the best I can do?
MonthStore monthStore = new MonthStore();
monthStore.open().then((months) {
TranStore tranStore = new TranStore();
tranStore.open().then((trans) {
// months[trans.monthId].name
});
});
I tried using Future.wait like this
// declare the stores
MonthStore monthStore = new MonthStore();
TranStore tranStore = new TranStore();
Future.wait(
[
getMonth(monthStore, intMonth),
// another call
]
)
.then...
Future<Map> getMonth(mnthStore, mnth) {
mnthStore.open()
.then((mnths) {
return mnths[mnth];
})
// need a return here!
});
but the editor says no return specified in the Future.
What am I missing here?
Future<Map> getMonth(mnthStore, mnth) {
return mnthStore.open() // <= here the return is important
.then((mnths) {
return mnths[mnth];
});
});

Resources