ReactiveUI Xamarin.ios bootsrapper for clean app launch - xamarin.ios

I have been looking for some examples on how to launch a xamarin.ios app in reactive-ui. I am also new in Xamarin.ios so maybe I am missing a crucial step. All examples I've found are either old, outdated or requires too much manual setup. I am looking to make a setup in a way where calling RoutedViewHost will resolve the UIViewController, which currently does not work and I get black screens. I assume there's no easy way to hook things up but wanted to check here first and ask the question.
Similar to how you can bootstrap a Xamarin.forms project, and how my code below successfully works in Xamarin.Forms, I'd like to start one in native.ios too.. So far I could not get the bindings and routing to work on initial app launch. I have below code in AppDelegate;
cs
var bootstrap = new AppBootstrapper();
var rootViewController = bootstrap.CreateMainView();
Window.RootViewController = rootViewController;
Window.MakeKeyAndVisible();
AppBootstrapper is a ReactiveObject, IScreen object and I try to register a simple LoginView for starters to test this out. View is in Storyboard, assigned to LoginViewController.
cs
public class AppBootstrapper: ReactiveObject, IScreen
public AppBootstrapper()
{
_routingState = new RoutingState();
InitializeIoC();
RegisterViews();
RegisterRoutableViewModals();
_routingState.Navigate.Execute(new LoginViewModel(this));
...
private void InitializeIoC()
{
Locator.CurrentMutable.RegisterConstant(this, typeof(IScreen));
}
private void RegisterViews()
{
Locator.CurrentMutable.Register(() => new LoginViewController(), typeof(IViewFor<LoginViewModel>));
}
private void RegisterRoutableViewModals()
{
Locator.CurrentMutable.Register(() => new LoginViewModel(), typeof(IRoutableViewModel), typeof(LoginViewModel).FullName);
}
public UIViewController CreateMainView()
{
return new RoutedViewHost();
}
...
public partial class LoginViewController : ReactiveViewController<LoginViewModel>
{
public LoginViewController()
{
ViewModel = new LoginViewModel();
this.WhenActivated(disposables =>
{
this.BindCommand(ViewModel, x => x.LoginCommand, x => x.btnLogin)
.DisposeWith(disposables);
this.Bind(ViewModel, x => x.Username, x => x.txtPassword.Text)
.DisposeWith(disposables);
this.Bind(ViewModel, x => x.Password, x => x.txtPassword.Text)
.DisposeWith(disposables);
this.OneWayBind(ViewModel, x => x.Warning, x => x.lblWarning.Text)
.DisposeWith(disposables);
this.OneWayBind(ViewModel, x => x.IsExecuteEnabled, x => x.btnLogin.Enabled)
.DisposeWith(disposables);
});
}
...
public class LoginViewModel: ReactiveObject, IRoutableViewModel
{
public string UrlPathSegment => "Login";
public IScreen HostScreen { get; }
public LoginViewModel(IScreen screen = null)
{
HostScreen = screen ?? Locator.Current.GetService<IScreen>();
LoginCommand = ReactiveCommand.CreateFromTask(async => Login());
this.WhenAnyValue(x => x.Username, x => x.Password, x => x.LoginCommand,
(user, pass, command) =>
!string.IsNullOrEmpty(user) &&
!string.IsNullOrEmpty(pass) &&
user.Length >= 3 &&
!IsCommandExecuting &&
!_disableLoginButton)
.DistinctUntilChanged()
.ToProperty(this, x => x.IsExecuteEnabled, out _isExecuteEnabled);
LoginCommand.IsExecuting
.ToProperty(this, x => x.IsCommandExecuting, out _isCommandExecuting);
}

Related

Unit testing that the swagger doc is correct without starting a server

I'd like to test that the swagger document is correct for my application (mainly, because I've added a strategy to generate custom OperationIds and I want to ensure they are correctly unique)
However, the only solutions I found are all using a "real" server (cf https://stackoverflow.com/a/52521454/1545567), which is not an option for me since I do not have the database, message bus, etc... when I launch the unit tests in CI...
At the moment, I have the following but it always generate 0 paths and 0 models ...
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using SampleCheckIn;
using Swashbuckle.AspNetCore.SwaggerGen;
using System;
using System.Linq;
using Xunit;
using SampleCheckIn.Def;
using Service.Utils;
using Swashbuckle.AspNetCore.Swagger;
using Microsoft.Extensions.Logging;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.FileProviders;
namespace D4Interop.Tests
{
public class TmpTest
{
[Fact]
public void Tmp()
{
var controllers = typeof(Startup).Assembly.GetTypes().Where(x => IsController(x)).ToList();
controllers.Any().Should().BeTrue();
var services = new ServiceCollection();
controllers.ForEach(c => services.AddScoped(c));
services.AddLogging(logging => logging.AddConsole());
services.AddControllers(); //here, I've also tried AddMvcCore and other ASP methods...
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("api", new OpenApiInfo { Title = Constants.SERVICE_NAME, Version = "_", Description = Constants.SERVICE_DESC });
//c.OperationFilter<SwaggerUniqueOperationId>(); //this is my filter that ensures the operationId is unique
c.CustomOperationIds(apiDesc =>
{
return apiDesc.TryGetMethodInfo(out var methodInfo) ? methodInfo.Name : null;
});
});
services.AddSingleton<IWebHostEnvironment>(new FakeWebHostEnvironment());
var serviceProvider = services.BuildServiceProvider();
var swaggerProvider = serviceProvider.GetRequiredService<ISwaggerProvider>();
var swagger = swaggerProvider.GetSwagger("api");
swagger.Should().NotBeNull();
swagger.Paths.Any().Should().BeTrue();
}
private bool IsController(Type x)
{
return typeof(Microsoft.AspNetCore.Mvc.ControllerBase).IsAssignableFrom(x);
}
}
internal class FakeWebHostEnvironment : IWebHostEnvironment
{
public FakeWebHostEnvironment()
{
}
public IFileProvider WebRootFileProvider { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public string WebRootPath { get => "/root"; set => throw new NotImplementedException(); }
public string EnvironmentName { get => "dev"; set => throw new NotImplementedException(); }
public string ApplicationName { get => "app"; set => throw new NotImplementedException(); }
public string ContentRootPath { get => "/"; set => throw new NotImplementedException(); }
public IFileProvider ContentRootFileProvider { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
}
}
Ok, I've finally found that I just need to mix the linked answer with my code :
[Fact]
public async Task TestSwagger()
{
var server = Host.CreateDefaultBuilder()
.ConfigureWebHostDefaults(options => { options.UseStartup<Startup>(); })
.Build();
var swagger = server.Services
.GetRequiredService<ISwaggerProvider>()
.GetSwagger("xxx"); //xxx should be the name of your API
swagger.Should().NotBeNull();
swagger.Paths.Any().Should().BeTrue();
swagger.Components.Schemas.Should().NotBeNull();
}

Configuring convention for all members with type in automapper

All my domain models have field public CurrencyId CurrencyId {get; set;}. All my view models have filed public CurrencyVm Currency {get; set;}. Automapper knows how to Map<CurrencyVm>(CurrencyId). How can I setup automatic convention, so I do not have to .ForMember(n => n.Currency, opt => opt.MapFrom(n => n.CurrencyId));?
ForAllMaps is the answer, thanks #LucianBargaoanu. This code kinda works, but hasn't been tested in all cases. Also I do not know how to check if exists mapping between choosen properties.
configuration.ForAllMaps((map, expression) =>
{
if (map.IsValid != null)
{
return; // type is already mapped (or not)
}
const string currencySuffix = "Id";
var currencyPropertyNames = map.SourceType
.GetProperties()
.Where(n => n.PropertyType == typeof(CurrencyId) && n.Name.EndsWith(currencySuffix))
.Select(n => n.Name.Substring(0, n.Name.Length - currencySuffix.Length))
.ToArray();
expression.ForAllOtherMembers(n =>
{
if (currencyPropertyNames.Contains(n.DestinationMember.Name, StringComparer.OrdinalIgnoreCase))
{
n.MapFrom(n.DestinationMember.Name + currencySuffix);
}
});
});

C# Bot Framework : Form flow set value for the field based on previous Answer [duplicate]

Hello I'm new to Microsoft Bot Framework and I have a question that I couldn't find an answer to.
I have a FormFlow that ask the user for some question, after a specific question I want the bot to do some logic and show messages accordingly (for example if the user selected option 1 then show message X and if the user selected option 2 show message Y).
Here is my code:
using Microsoft.Bot.Builder.FormFlow;
using Microsoft.Bot.Builder.Dialogs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Bot_CRM.FormFlow
{
public enum RequestOptions { Unknown, CheckStatus, CreateCase };
[Serializable]
public class CaseFormFlow
{
public RequestOptions RequestType;
[Prompt("What is your first name?")]
public string FirstName;
public string LastName;
public string ContactNumber;
[Prompt("Please enter your id")]
public string Id;
public static IForm<CaseFormFlow> BuildForm()
{
OnCompletionAsyncDelegate<CaseFormFlow> processRequest = async (context, state) =>
{
await context.PostAsync($#"Thanks for your request");
};
return new FormBuilder<CaseFormFlow>()
.Message("Hello and welcom to my service desk bot")
.Field(nameof(FirstName))
.Message("hello {FirstName}")
.Field(nameof(Id))
.Field(nameof(RequestType)) =>
//here if user select 1 start flow of check status and if user select 2 start flow of create case
.AddRemainingFields()
.Message("Thank you request. Our help desk team will get back to you shortly.")
.OnCompletion(processRequest)
.Build();
}
}
}
Updated code after Ezequiel's suggestion:
return new FormBuilder<CaseFormFlow>()
.Message("Hello and welcom to my service desk bot")
.Field(nameof(FirstName))
.Message("hello {FirstName}")
.Field(new FieldReflector<CaseFormFlow>(nameof(RequestType))
.SetActive(state => state.AskUserForRequestType)
.SetNext((value, state) =>
{
var selection = (RequestOptions)value;
if (selection == RequestOptions.CheckStatus)
{
return new NextStep(new[] { nameof(Id) });
}
else
{
return new NextStep();
}
}))
Thanks in advance for the help
This is a great question.The key thing is to use the SetActive and SetNext methods of the Field<T> class. You should consider using the FieldReflector class; though you can implement your own IField.
SetActive is described in the Dynamic Fields section of the FormFlow documentation. Basically it provides a delegate that enables the field based on a condition.
SetNext will allow you to decide what step of the form should come next based on your custom logic.
You can take a look to the ContosoFlowers sample. In the Order form; something similar is being done.
public static IForm<Order> BuildOrderForm()
{
return new FormBuilder<Order>()
.Field(nameof(RecipientFirstName))
.Field(nameof(RecipientLastName))
.Field(nameof(RecipientPhoneNumber))
.Field(nameof(Note))
.Field(new FieldReflector<Order>(nameof(UseSavedSenderInfo))
.SetActive(state => state.AskToUseSavedSenderInfo)
.SetNext((value, state) =>
{
var selection = (UseSaveInfoResponse)value;
if (selection == UseSaveInfoResponse.Edit)
{
state.SenderEmail = null;
state.SenderPhoneNumber = null;
return new NextStep(new[] { nameof(SenderEmail) });
}
else
{
return new NextStep();
}
}))
.Field(new FieldReflector<Order>(nameof(SenderEmail))
.SetActive(state => !state.UseSavedSenderInfo.HasValue || state.UseSavedSenderInfo.Value == UseSaveInfoResponse.Edit)
.SetNext(
(value, state) => (state.UseSavedSenderInfo == UseSaveInfoResponse.Edit)
? new NextStep(new[] { nameof(SenderPhoneNumber) })
: new NextStep()))
.Field(nameof(SenderPhoneNumber), state => !state.UseSavedSenderInfo.HasValue || state.UseSavedSenderInfo.Value == UseSaveInfoResponse.Edit)
.Field(nameof(SaveSenderInfo), state => !state.UseSavedSenderInfo.HasValue || state.UseSavedSenderInfo.Value == UseSaveInfoResponse.Edit)
.Build();
}
}
}

How do you Bind an Image to the HighlightedImage-Property of an ImageView?

I'm currently trying to bind two images to an iOS ImageView via MvvmCross.
One should be displayed when the ImageView is in 'default' state, the other one when the ImageView is highlighted.
By the following code I can bind the Image for the default state. But how do I bind the one for 'highlighted' state?
public CategoryCell(IntPtr handle): base(string.Empty, handle)
{
_imageViewLoader = new MvxImageViewLoader(() => this.imageView);
this.DelayBind(() =>
{
var set = this.CreateBindingSet<CategoryCell, MaterialCategory>();
set.Bind(titleLabel).To(materialCategory => materialCategory.Label);
set.Bind(_imageViewLoader).To(materialCategory => materialCategory.ImageActiveUri);
set.Bind(this).For(cell => cell.Selected).To(materialCategory => materialCategory.IsSelected);
set.Apply();
});
}
Another approach if you do not need image loading i.e. for lots of static UX.
You can set up as follows -
_imageView = new UIImageView(UIImage.FromFile("some/image/off.png"))
{
HighlightedImage = UIImage.FromFile("some/image/on.png")
};
And bind it e.g. an "Enabled" Property -
this.DelayBind(() =>
{
var set = this.CreateBindingSet<SomeView, SomeViewModel>();
set.Bind(_imageView).For(v => v.Highlighted).To(vm => vm.Enabled);
set.Apply();
});
And don't forget to add Highlighted to your LinkerPleaseInclude.cs.
Hope this helps
I think the best solution is to introduce an extra property ImageUri. In the setter of your IsSelected you set the ImageUri dependend on the selection state.
ViewModel:
public class MaterialCategory : MvxViewModel
{
//...
public string ImageActiveUri { ... } // call UpdateImageUri() here, too
public string ImageInactiveUri { ... } // call UpdateImageUri() here, too
public string ImageUri { ... }
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
UpdateImageUri();
RaisePropertyChanged(() => IsSelected);
}
}
private void UpdateImageUri()
{
ImageUri = IsSelected ? ImageActiveUri : ImageInactiveUri;
}
}
Binding:
set.Bind(_imageViewLoader).To(materialCategory => materialCategory.ImageUri);
// instead of:
// set.Bind(_imageViewLoader).To(materialCategory => materialCategory.ImageActiveUri);

Why use Automappers ValueResolver?

Why do this
Mapper.CreateMap<MyObject, AnotherObject>().
ForMember(x => x.DateAsString, m => m.ResolveUsing<StringToDateTimeFormatter>());
private class StringToDateTimeFormatter : ValueResolver<DateTime, string>
{
protected override string ResolveCore(DateTimesource)
{
return source.ToString("yyyy-MM-dd");
}
}
when you can do this
Mapper.CreateMap<MyObject, AnotherObject>().
ForMember(x => x.DateAsString, m => m.MapFrom(x => x.Date.ToString("yyy-MM-dd")));
???
Update
Here's an example on how to do more complex business logic
Mapper.CreateMap<MyObject, AnotherObject>().
ForMember(x => x.DateAsString, m => m.MapFrom(n => MyMethod(n.DateAsString)));
private object MyMethod(string dateTime)
{
if(!MyDomainObjectIsValid(dateTime))
{
throw new MyValidationException();
}
// do more stuff
}
I still don't see the need for a ValueResolver...
Obviously for your example it is more reasonable to use just MapFrom.
ValueResolvers are needed for more complicated cases. For example when you need to do some validation and throw exception accordingly.
EDIT
ValueResolvers provide access to the destination type and value. Here is small example.
public class FakeResolver : IValueResolver
{
public ResolutionResult Resolve(ResolutionResult source)
{
if (source.Context.DestinationType == typeof(string) && source.Context.DestinationValue == "test")
throw new Exception();
return source;
}
}

Resources