ReactiveUI Xamarin iOS routing - xamarin

I started experimenting with Reactive UI in Xamarin iOS and i'm confused to how I should handle routing.
Let's take the typical 'LoginViewModel' which has something like:
public class LoginViewModel : ReactiveObject
{
private readonly ReactiveCommand loginCommand;
public ReactiveCommand LoginCommand => this.loginCommand;
string username;
public string Username
{
get { return username; }
set { this.RaiseAndSetIfChanged(ref username, value); }
}
string password;
public string Password
{
get { return password; }
set { this.RaiseAndSetIfChanged(ref password, value); }
}
public LoginViewModel()
{
var canLogin = this.WhenAnyValue(
x => x.Username,
x => x.Password,
(u, p) => !string.IsNullOrEmpty(u) && !string.IsNullOrEmpty(p)
);
this.loginCommand = ReactiveCommand.CreateFromTask(async () =>
{
//Simulate login
await Task.Delay(2000);
return true;
}, canLogin);
}
}
And the ViewDidLoad (Controller):
this.WhenActivated(d =>
{
this.Bind(this.ViewModel, x => x.Username, x => x.Username.Text).DisposeWith(d);
this.Bind(this.ViewModel, x => x.Password, x => x.Password.Text).DisposeWith(d);
this.BindCommand(this.ViewModel, x => x.LoginCommand, x => x.LoginButton).DisposeWith(d);
});
This effectively binds the values of those UITextFields and Login button enable states + OnTouchUpInside
Now, in de documentation you can find the following about vm-routing: Android, iOS Native: Very difficult to make work
So what would be my options here ?
Expose a DidLogIn property (bool) and listen (in the view) on that with:
this.WhenAnyValue(x => x.ViewModel.DidLogIn)
.ObserveOn(RxApp.MainThreadScheduler)
.Subscribe(() => {
//Routing logic here
});
Are there other ways to handle view-routing (not vm-routing), I can find very little information about this

ReactiveUI's routing on Xamarin Forms is very easy, Xamarin Android/iOS is different history, but you can try ReactiveUI's interactions here is an example:
public class LoginViewModel : ReactiveObject
{
private readonly Interaction<Unit, Unit> _navigate;
public Interaction<Unit, Unit> Navigate => Navigate;
public ReactiveCommand<Unit,bool> LoginCommand { get; set; }
public LoginViewModel()
{
var canLogin = this.WhenAnyValue(
x => x.Username,
x => x.Password,
(u, p) => !string.IsNullOrEmpty(u) && !string.IsNullOrEmpty(p));
_navigate = new Interaction<Unit, Unit>();
LoginCommand = ReactiveCommand.CreateFromTask<Unit, bool>(async _ =>
{
/*Logic here*/
return true;
}, canLogin);
LoginCommand.Subscribe(async result =>
{
if (result)//this logic gets executed on your view by registering a handler :D
await await _navigate.Handle(Unit.Default) ;
else
{}
});
}
}
So in your view
this.WhenActivated(disposables =>
{
//bindings...
//Register a handler:
ViewModel.Navigate.RegisterHandler(async interaction =>
{
await NavigationLogic();
interaction.SetOutput(Unit.Default);
}).DisposeWith(disposables);
});
The example is not perfect, but is one way to do it.
I hope this helps you, you can find more on interactions in: https://reactiveui.net/docs/handbook/interactions/
Also there is an example in Xamarin Android + ReactiveUI: https://github.com/bl8/ReactivePhoneword
Regards

Related

MassTransit/Quartz.NET schedule works for InMemory but not in persistent mode

When using InMemoryMessageScheduler with Quartz, the ScheduledMessage actually gets scheduled and the "Message" is published at the defined time.
The problem is when the Quartz with persisting option is used, the message is persisted into the database, the ScheduledMessage is consumed by the ScheduleMessageConsumer BUT the "Message" is never published at the defined time.
I've used Sample-GettingStarted and I've added the next changes:
QuartzConfig:
public class QuartzConfig : Dictionary<string, string>
{
public QuartzConfig(string connectionString)
{
this["quartz.scheduler.instanceName"] = "MassTransit-Scheduler";
this["quartz.scheduler.instanceId"] = "AUTO";
this["quartz.serializer.type"] = "json";
this["quartz.jobStore.type"] = "Quartz.Impl.AdoJobStore.JobStoreTX, Quartz";
this["quartz.jobStore.driverDelegateType"] = "Quartz.Impl.AdoJobStore.StdAdoDelegate, Quartz";
this["quartz.jobStore.tablePrefix"] = "QRTZ_";
this["quartz.jobStore.dataSource"] = "myDS";
this["quartz.dataSource.myDS.provider"] = "Npgsql";
this["quartz.dataSource.myDS.connectionString"] = connectionString;
this["quartz.jobStore.useProperties"] = "true";
}
public NameValueCollection ToNameValueCollection()
{
return this.Aggregate(new NameValueCollection(), (seed, current) =>
{
seed.Add(current.Key, current.Value);
return seed;
});
}
}
Configuration:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddMassTransit(x =>
{
x.AddConsumer<MessageConsumer>();
var scheduler = CreateScheduler();
x.UsingRabbitMq((context,cfg) =>
{
cfg.ReceiveEndpoint("quartz", endpoint =>
{
endpoint.Consumer(() => new ScheduleMessageConsumer(scheduler));
endpoint.Consumer(() => new CancelScheduledMessageConsumer(scheduler));
cfg.UseMessageScheduler(endpoint.InputAddress);
});
cfg.ConfigureEndpoints(context);
});
});
services.AddMassTransitHostedService();
services.AddHostedService<Worker>();
});
static IScheduler CreateScheduler()
{
var dbConnectionString = "Host=localhost;Database=scheduler;Port=5432;Password=pass;User ID=user;Pooling=true;MaxPoolSize=200;Enlist=true";
var quartzConfig = new QuartzConfig(dbConnectionString)
.ToNameValueCollection();
ISchedulerFactory schedulerFactory = new StdSchedulerFactory(quartzConfig);
return schedulerFactory.GetScheduler().GetAwaiter().GetResult();
}
Worker.cs:
public class Worker : BackgroundService
{
readonly IBus _bus;
public Worker(IBus bus)
{
_bus = bus;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await _bus.CreateMessageScheduler().SchedulePublish(DateTime.UtcNow + TimeSpan.FromSeconds(5), new Message { Text = $"I really hope this is scheduled {DateTime.Now}" });
await Task.Delay(10000, stoppingToken);
}
}
}
Any ideas?
The Quartz Integration package connects a bus observer to handle the start/stop of Quartz.NET, as shown in the source. Unfortunately the documentation isn't great on how to do it.

DisplayAlert Xamarim forms

I Have this:
var accept = await DisplayAlert("Title", "ask", "yes", "not");
I would like this displayalert to be visible for 5 seconds if the user does not choose any option the displayalert disappears
how can i do this?
Welcome to SO !
Unfortunately , Xamarin Forms not provides some like Dismiss method for alert windown . However , we can use other ways to achieve that . Such as using Xamarin.Forms DependencyService .
First , we can create a IShowAlertService interface in xamarin forms :
public interface IShowAlertService
{
Task<bool> ShowAlert(string title,string message,string ok, string cancel);
}
Next in iOS , Create its implement class :
[assembly: Dependency(typeof(ShowAlertService))]
namespace XamarinTableView.iOS
{
class ShowAlertService : IShowAlertService
{
TaskCompletionSource<bool> taskCompletionSource;
public Task<bool> ShowAlert(string title, string message, string ok, string cancel)
{
taskCompletionSource = new TaskCompletionSource<bool>();
var okCancelAlertController = UIAlertController.Create(title, message, UIAlertControllerStyle.Alert);
//Add Actions
okCancelAlertController.AddAction(UIAlertAction.Create(ok, UIAlertActionStyle.Default, alert => {
Console.WriteLine("Okay was clicked");
taskCompletionSource.SetResult(true);
}));
okCancelAlertController.AddAction(UIAlertAction.Create(cancel, UIAlertActionStyle.Cancel, alert => {
Console.WriteLine("Cancel was clicked");
taskCompletionSource.SetResult(true);
}));
UIWindow window = UIApplication.SharedApplication.KeyWindow;
var viewController = window.RootViewController;
//Present Alert
viewController.PresentViewController(okCancelAlertController, true, null);
Device.StartTimer(new TimeSpan(0, 0, 3), () =>
{
taskCompletionSource.SetResult(false);
Device.BeginInvokeOnMainThread(() =>
{
okCancelAlertController.DismissViewController(true,null);
// interact with UI elements
Console.WriteLine("Auto Dismiss");
});
return false; // runs again, or false to stop
});
return taskCompletionSource.Task;
}
}
And in Android , also do that :
[assembly: Dependency(typeof(ShowAlertService))]
namespace XamarinTableView.Droid
{
class ShowAlertService : IShowAlertService
{
TaskCompletionSource<bool> taskCompletionSource;
public Task<bool> ShowAlert(string title, string message, string ok, string cancel)
{
taskCompletionSource = new TaskCompletionSource<bool>();
Android.App.AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.Instance);
AlertDialog alert = dialog.Create();
alert.SetTitle("Title");
alert.SetMessage("Complex Alert");
alert.SetButton("OK", (c, ev) =>
{
// Ok button click task
Console.WriteLine("Okay was clicked");
taskCompletionSource.SetResult(true);
});
alert.SetButton2("CANCEL", (c, ev) => {
Console.WriteLine("Cancel was clicked");
taskCompletionSource.SetResult(false);
});
alert.Show();
Device.StartTimer(new TimeSpan(0, 0, 3), () =>
{
if (taskCompletionSource.Equals(null))
taskCompletionSource.SetResult(false);
Device.BeginInvokeOnMainThread(() =>
{
alert.Dismiss();
// interact with UI elements
Console.WriteLine("Auto Dismiss");
});
return false; // runs again, or false to stop
});
return taskCompletionSource.Task;
}
}
}
Here in Android , need to create a static Instance in MainActivity . Becasue we need to use it in ShowAlertService :
public class MainActivity : FormsAppCompatActivity
{
internal static MainActivity Instance { get; private set; }
protected override void OnCreate(Bundle savedInstanceState)
{
// ...
Instance = this;
}
//...
}
Now in Xamarin Forms , we can involke it as follow :
private async void Button_Clicked(object sender, EventArgs e)
{
bool value = await DependencyService.Get<IShowAlertService>().ShowAlert("Alert", "You have been alerted", "OK", "Cancel");
Console.WriteLine("Value is : "+value);
}
The effect as follow :

ASP.NET Core - Session not getting saved

I'm trying to save my session in ASP.NET Core, but it is not getting saved.
I have looked at other answers, suggesting to change CookiePolicyOptions and nothing has worked so far. I have another project with the exact same code (presumably), and it works there but not in this project.
In my controller I have:
[HttpPost]
public IActionResult AddToPlan(int mealId)
{
PlanCart planCart = GetPlanCart();
planCart.AddItem(mealId);
SavePlanCart(planCart);
// ALWAYS 1
var y = planCart.returnList();
foreach (var x in y)
{
var z = x; // For debug purposes
}
return RedirectToAction("Index");
}
private PlanCart GetPlanCart()
{
PlanCart planCart = HttpContext.Session.GetJson<PlanCart>("PlanCart") ?? new PlanCart();
return planCart;
}
private void SavePlanCart(PlanCart planCart)
{
HttpContext.Session.SetJson("PlanCart", planCart);
}
I have a class with extension methods:
public static class SessionsExtensions
{
public static void SetJson(this ISession session, string key, object value)
{
session.SetString(key, JsonConvert.SerializeObject(value));
}
public static T GetJson<T>(this ISession session, string key)
{
var sessionData = session.GetString(key);
return sessionData == null
? default(T) : JsonConvert.DeserializeObject<T>(sessionData);
}
}
Startup class:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAuthentication();
app.UseSession();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
app.UseCookiePolicy();
}
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddSession();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddMemoryCache();
I have checked my session. The session DOES exist but every time the count of the PlanCartList is 1 and previous items are lost.
If anybody could help me it would be very much appreciated because I'm losing my mind here!

Saga handling undeliverable messages

From documentation here:
If the configuration of an endpoint changes, or if a message is
mistakenly sent to an endpoint, it is possible that a message type is
received that does not have any connected consumers. If this occurs,
the message is moved to a _skipped queue (prefixed by the original
queue name). The original message content is retained, and additional
headers are added to indicate the host which moved the message.
Should I expect the same behavior for a saga when some message tried to be delivered to the saga but saga instance is not created yet?
In other words, if want to handle message in saga but that message is not a trigger for saga creation. I would expect to see that messages in _skipped queue, but in reality I don't see them either in _skipped or _error queues. Also I can see that the massage successfully was delivered to the saga queue and successfully was consumed somehow without any warnings or errors. Who has consumed the message?
UPDATED
State machine:
public class TestState : SagaStateMachineInstance, IVersionedSaga
{
public Guid CorrelationId { get; set; }
public Guid Id { get; set; }
public string CurrentState { get; set; }
public int Version { get; set; }
public DateTime TimeStart { get; set; }
public int Progress { get; set; }
}
public class TestStateMachine : MassTransitStateMachine<TestState>
{
public TestStateMachine()
{
InstanceState(x => x.CurrentState);
Event(() => ProcessingStarted, x => x.CorrelateById(context => context.Message.Id));
Event(() => ProgressUpdated, x => x.CorrelateById(context => context.Message.Id));
Event(() => ProcessingFinished, x => x.CorrelateById(context => context.Message.Id));
Initially(
When(ProcessingStarted)
.Then(context =>
{
context.Instance.TimeStart = DateTime.Now;
})
.TransitionTo(Processing)
);
During(Processing,
When(ProgressUpdated)
.Then(context =>
{
context.Instance.Progress = context.Data.Progress;
}),
When(ProcessingFinished)
.TransitionTo(Processed)
.ThenAsync(async context =>
{
await context.Raise(AllDone);
})
);
During(Processed,
When(AllDone)
.Then(context =>
{
Log.Information($"Saga {context.Instance.Id} finished");
})
.Finalize());
SetCompletedWhenFinalized();
}
public State Processing { get; set; }
public State Processed { get; set; }
public Event AllDone { get; set; }
public Event<ProcessingStarted> ProcessingStarted { get; set; }
public Event<ProgressUpdated> ProgressUpdated { get; set; }
public Event<ProcessingFinished> ProcessingFinished { get; set; }
}
public interface ProcessingStarted
{
Guid Id { get; }
}
public interface ProgressUpdated
{
Guid Id { get; }
int Progress { get; }
}
public interface ProcessingFinished
{
Guid Id { get; }
}
Configuration
var repository = new MongoDbSagaRepository<TestState>(Environment.ExpandEnvironmentVariables(configuration["MongoDb:ConnectionString"]), "sagas");
var services = new ServiceCollection();
services.AddSingleton(context => Bus.Factory.CreateUsingRabbitMq(x =>
{
IRabbitMqHost host = x.Host(new Uri(Environment.ExpandEnvironmentVariables(configuration["MassTransit:ConnectionString"])), h => { });
x.ReceiveEndpoint(host, "receiver_saga_queue", e =>
{
e.StateMachineSaga(new TestStateMachine(), repository);
});
x.UseRetry(r =>
{
r.Incremental(10, TimeSpan.FromMilliseconds(10), TimeSpan.FromMilliseconds(50));
r.Handle<MongoDbConcurrencyException>();
r.Handle<UnhandledEventException>();
});
x.UseSerilog();
}));
var container = services.BuildServiceProvider();
var busControl = container.GetRequiredService<IBusControl>();
busControl.Start();
Later, when I publish ProgressUpdated event
busControl.Publish<ProgressUpdated>(new
{
Id = id,
Progress = 50
});
I would expect it will raise UnhandledEventException and move the massage to the _error queue but in reality I can see that message appeared in the queue, consumed somehow and I don't see it in either _error or _skipped queues. MongoDB Saga storage also doesn't have any new saga instances created.
What am I doing wrong? I need to configure saga the way that if it get ProgressUpdated event when the saga instance is not created it would be retried later when the instance is ready to accept it.
SOLUTION
Reconfigure the event ProgressUpdated and ProcessingFinished as
Event(() => ProgressUpdated, x =>
{
x.CorrelateById(context => context.Message.Id);
x.OnMissingInstance(m => m.Fault());
});
Event(() => ProcessingFinished, x =>
{
x.CorrelateById(context => context.Message.Id);
x.OnMissingInstance(m => m.Fault());
});
and catching SagaException in UseRetry later
r.Handle<SagaException>();
did the work!
Thanks #Alexey Zimarev for sharing the knowledge!
If a message should be handled by a saga but there is no matching instance, the OnMissingInstance callback is called.
Reconfigure ProgressUpdated and ProcessingFinished:
Event(() => ProgressUpdated, x =>
{
x.CorrelateById(context => context.Message.Id);
x.OnMissingInstance(m => m.Fault());
});
Event(() => ProcessingFinished, x =>
{
x.CorrelateById(context => context.Message.Id);
x.OnMissingInstance(m => m.Fault());
});
and catch SagaException in the retry filter:
r.Handle<SagaException>();

How to working with data from client side using Observables

I am using the Grid of Kendo (Angular 2) for Add/Edit/Delete a Row in the grid:
http://www.telerik.com/kendo-angular-ui/components/grid/editing/
In the original Code, the data is obtained from a rest service like this:
private fetch(action: string = "", data?: Product): Observable<Product[]> {
return this.jsonp
.get(`http://demos.telerik.com/kendo-ui/service/Products/${action}? callback=JSONP_CALLBACK${this.serializeModels(data)}`)
.map(response => response.json());
}
But, I want to work with a array for add/edit/delete rows in memory. Next, I want to do click in the button submit and send the data (with all my changes) to the server.
My solution for this is like this:
https://gist.github.com/joedayz/9e318a47d06a7a8c2170017eb133a87e
Overview:
I declare an array:
private view: Array = [{ProductID: 1, ProductName: "pelotas", Discontinued: undefined, UnitsInStock: 80}];
and override the fetch method like this:
private fetch(action: string = "", data?: Product): Observable<Product[]> {
/*return this.jsonp
.get(`http://demos.telerik.com/kendo-ui/service/Products/${action}?callback=JSONP_CALLBACK${this.serializeModels(data)}`)
.map(response => response.json());*/
debugger;
if(action=="create"){
var product : Product = new Product(-1, data.ProductName, data.Discontinued, data.UnitsInStock);
this.view.push(product);
}else if(action=="update"){
var indice = this.view.indexOf(data);
if(indice>=0)
this.view[indice] = (JSON.parse(JSON.stringify(data)));
}else if(action=="destroy"){
var indice = this.view.indexOf(data);
if(indice>=0)
this.view.splice(indice, 1);
}
return Observable.of(this.view);
}
My Question is: Exists some way of communicate the create/update/delete of items in my array of a simple or reactive form to my grid?
As you are using in-memory array you do not need to use Observables. The Grid component is already bound to the array, thus it is just necessary to manipulate the data. For example:
export class AppComponent {
public dataItem: Product;
#ViewChild(GridEditFormComponent) protected editFormComponent: GridEditFormComponent;
private view: Array<Product> = [{ ProductID: 1, ProductName: "pelotas", Discontinued: undefined, UnitsInStock: 80 }];
public onEdit(dataItem: any): void {
this.dataItem = Object.assign({}, dataItem);
}
public onCancel(): void {
this.dataItem = undefined;
}
public addProduct(): void {
this.editFormComponent.addProduct();
}
public onSave(product: Product): void {
if (product.ProductID === undefined) {
this.createProduct(product);
} else {
this.saveProducts(product);
}
}
public onDelete(e: Product): void {
this.deleteProduct(e);
}
public saveProducts(data: Product): void {
var index = this.view.findIndex(x => x.ProductID === data.ProductID);
if (index !== -1) {
this.view = [
...this.view.slice(0, index),
data,
...this.view.slice(index + 1)
];
}
}
public createProduct(data: Product): void {
this.view = [...this.view, data];
}
public deleteProduct(data: Product): void {
this.view = this.view.filter(x => x.ProductID !== data.ProductID);
}
}

Resources