how to do Shell Navigation with complex objects in .net MAUI or Xamarin Forms? - xamarin.forms

Recently I've started using shell and I noticed an improvement in page transitioning and a bug I was facing using NavigationPage was fixed just by replacing it with shell.
So I was excited to use it.
However soon after I realized I can't send objects from page to page through shell like I could using a constructer of a page. I searched a bit and now know that shell passes strings only. I turned the object into JSON but then faced an exception due to long URI length.
Honestly, I am disappointed. I thought something this important would be implemented in shell... but In any case, how do you guys work around this?

For Maui.
See (Xamarin) Process navigation data using a single method.
Also mentioned in maui issue. Adapting the Maui invocation there:
await Shell.Current.GoToAsync("//myAwesomeUri",
new Dictionary { {"data", new MyData(...)} });
This uses IQueryAttributable and ApplyQueryAttributes to pass an IDictionary<string, object> query.
(The Xamarin example shows IDictionary<string, string>, but its , object in Maui, so you can pass any object values.)
Thus the string parameters you pass can be used to look up corresponding objects.
From that (Xamarin) doc (modified to show looking up an object):
public class MonkeyDetailViewModel : IQueryAttributable, ...
{
public MyData Data { get; private set; }
public void ApplyQueryAttributes(IDictionary<string, object> query)
{
Data = (MyData)query["data"];
}
...
}
For Xamarin Forms, the limitation to string values makes this a bit ugly. One approach is to have a static that holds possible objects, which you look up using a string. This is tolerable when the objects are all pre-defined, but is a bit clumsy if you are manually altering those objects.
public class MonkeyDetailViewModel : IQueryAttributable, ...
{
public static Dictionary<string, MyData> KeyedData;
// "static": One-time class constructor.
public static MonkeyDetailViewModel()
{
KeyedData = new Dictionary<string, MyData>();
KeyedData["data1"] = new MyData(...);
// ... other versions of the data ...
}
public MyData Data { get; private set; }
public void ApplyQueryAttributes(IDictionary<string, string> query)
{
string whichData = query["data"]; // In example, gets "data1".
Data = KeyedData[whichData];
}
...
}
Usage:
await Shell.Current.GoToAsync("//myAwesomeUri",
new Dictionary { {"data", "data1"} });
Xamarin NOTE: The static dictionary makes it possible to maintain multiple instances of MyData. The "hack" alternative is to have MyData Data be static, and explicitly set it before GoToAsync - but this is risky if you ever might have a MonkeyDetailView on nav stack, go to a second one, then go back to first one - you'll have overwritten the Data seen by the first view.

Related

MAUI+ASP.NET DTOs

I have a project consisting of 2 parts:
ASP.NET API using Entity Framework
.NET MAUI Client App
I use DTOs for comunication from/to the API in order not to expose other properties of my entities. Thanks to this approach I was able to separate Entity data and data that are sent from the API.
At first I used these DTOs also in the MAUI UI. But after some time I started to notice that they contains UI-specific properties, attributes or methods that have no purpose for the API itself, so they are redundant in requests.
EXAMPLE:
1 - API will receive request from MAUI to get exercise based on it's name
2- ExerciseService returns: ExerciseEntity and ExerciseController use AutoMapper to Map ExerciseEntity -> ExerciseDto ommiting ExerciseId field (only admin can see this info in the DB) and returning it in the API response
3 - MAUI receives from the API ExerciseDto. But in the client side it also want to know if data from ExerciseDto are collapsed in the UI. So because of that I add IsCollapsed property into the ExerciseDto. But now this is a redundant property for the API, because I dont want to persist this information in the database.
QUESTIONS:
Should I map these DTOs to new objects on the client side ?
Or how to approach this problem ?
Is there an easier way how to achieve the separation ?
Because having another mapping layer will add extra complexity and a lot of duplicate properties between DTOs and those new client objects.
Normally if you use clean architecture approach your DTOs shoud contain no attributes and other specific data relevant just for some of your projects, to be freely usable by other projects in a form of dependency.
Then you'd have different approaches to consume DTOs in a xamarin/maui application, for example:
APPROACH 1.
Mapping (of course) into a class that is suitable for UI. Here you have some options, use manual mapping, write your own code that uses reflection or use some third party lib using same reflection. Personally using all of them, and when speaking of third party libs Mapster has shown very good to me for api and mobile clients.
APPROACH 2.
Subclass DTO. The basic idea is to deserialize dto into the derived class, then call Init(); if needed. All properties that you manually implemented as new with OnPropertyChanged will update bindings after being popupated by deserializer/mapper and you alse have a backup plan to call RaiseProperties(); for all of the props, even thoses who do not have OnPropertyChanged in place so they can update bindings if any.
Example:
our Api DTO
public class SomeDeviceDTO
{
public int Id { get; set; }
public int Port { get; set; }
public string Name { get; set; }
}
Our derived class for usage in mobile client:
public class SomeDevice : SomeDeviceDTO, IFromDto
{
// we want to be able to change this Name property in run-time and to
// reflect changes so we make it bindable (other props will remain without
// OnPropertyChanged BUT we can always update all bindings in code if needed
// using RaiseProperties();):
private string _name;
public new string Name
{
get { return _name; }
set
{
if (_name != value)
{
_name = value;
OnPropertyChanged();
}
}
}
// ADD any properties you need for UI
// ...
#region IFromDto
public void Init()
{
//put any code you'd want to exec after dto's been imported
// for example to fill any new prop with data derived from what you received
}
public void RaiseProperties()
{
var props = this.GetType().GetProperties();
foreach (var property in props)
{
if (property.CanRead)
{
OnPropertyChanged(property.Name);
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
public interface IFromDto : INotifyPropertyChanged
{
//
// Summary:
// Can initialize model after it's being loaded from dto
void Init();
//
// Summary:
// Notify all properties were updated
void RaiseProperties();
}
We can get it like: var device = JsonConvert.DeserializeObject<SomeDevice>(jsonOfSomeDeviceDTO);
We then can call Init(); if needed..
Feel free to edit this answer to add more approaches..

Use case example for copyToRealm() in Realm

Could someone give a simple use case example why someone would use copyToRealm() instead of createObject() ?
It is not clear to me why and when would anyone use copyToRealm() if there is createObject().
In the example here they seem pretty much the same https://realm.io/docs/java/latest/ .
copyToRealm() takes an unmanaged object and connects it to a Realm, while createObject() creates an object directly in a Realm.
For example it is very useful when you copy objects generated by GSON - returned from your Rest API into Realm.
realm.createObject() also returns a RealmProxy instance and is manipulated directly and therefore creates N objects to store N objects, however you can use the following pattern to use only 1 instance of object to store N objects:
RealmUtils.executeInTransaction(realm -> {
Cat defaultCat = new Cat(); // unmanaged realm object
for(CatBO catBO : catsBO.getCats()) {
defaultCat.setId(catBO.getId());
defaultCat.setSourceUrl(catBO.getSourceUrl());
defaultCat.setUrl(catBO.getUrl());
realm.insertOrUpdate(defaultCat);
}
});
But to actually answer your question, copyToRealmOrUpdate() makes sense if you want to persist elements, put them in a RealmList<T> and set that RealmList of newly managed objects in another RealmObject. It happens mostly if your RealmObject classes and the downloaded parsed objects match.
#JsonObject
public class Cat extends RealmObject {
#PrimaryKey
#JsonField(name="id")
String id;
#JsonField(name="source_url")
String sourceUrl;
#JsonField(name="url")
String url;
// getters, setters;
}
final List<Cat> cats = //get from LoganSquare;
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
Person person = realm.where(Person.class).equalTo("id", id).findFirst();
RealmList<Cat> realmCats = new RealmList<>();
for(Cat cat : realm.copyToRealmOrUpdate(cats)) {
realmCats.add(cat);
}
person.setCats(realmCats);
}
});

How to allow client pass my object through webservice?

Sorry if this is stupid question, because I'm a bit confused about .NET remoting and distributed object.
I want to write a webservice, and in one of its methods, I want user to pass one my object's instance as parameter. It will greatly reduces number of parameters, and help user call this method more effectively. I create some class, but when distributing them to client, only class name remains, all properties and methods are gone, just like this
public class CameraPackages
{
private readonly List<CameraPackage> _packages;
public CameraPackages()
{
_packages = new List<CameraPackage>();
}
public void AddNewCamera(CameraPackage package)
{
_packages.Add(package);
}
public void RemoveCamera(CameraPackage package)
{
if(_packages.Contains(package))
_packages.Remove(package);
else
throw new ArgumentException();
}
}
into this: (in Reference.cs)
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "2.0.50727.3082")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://tempuri.org/")]
public partial class CameraPackages {
}
How can I do to allow user use my object?
Thank you so much.
Web Services will only serialise public properties, so you can't do that (in that way) using web services.
You will need to manage your list of objects client side, then send the data in a transfer object (a class with just properties).
Have a look at this.

Attach additional ObjectSets to ObjectContext from separate project

I hope this makes sense. I have a ASP.NET web application that uses Entity Framework. I have added a couple of custom tables to the db and created a separate project to handle the CRUD operations for those tables. I chose the separate project because I don't want future upgrades to the application to overwrite my custom features.
My problem is this. How do I attach/combine my custom ObjectContext to the ObjectContext of the application? I want to use the same UnitOfWorkScope (already in the application) to maintain the one ObjectContext instance per HTTP request. Again, I don't want to add my ObjectSets to the application's ObjectContext for my reason listed above.
Here is some code:
Widget.cs
public partial class Widget
{
public Widget()
{
}
public int WidgetId {get;set;}
public string WidgetName {get;set;}
}
WidgetObjectContext.cs
public partial class WidgetObjectContext : ObjectContext
{
private readonly Dictionary<Type, object> _entitySets;
public ObjectSet<T> EntitySet<T>()
where T : BaseEntity
{
var t = typeof(T);
object match;
if(!_entitySets.TryGetValue(t, out match))
{
match = CreateObjectSet<T>();
_entitySets.Add(t, match);
}
return (ObjectSet<T>)match;
}
public ObjectSet<Widget> Widgets
{
get
{
if((_widgets == null))
{
_widgets = CreateObjectSet<Widget>();
}
return _widget;
}
}
private ObjectSet<Widget> _widgets;
In my WidgetManager class if I was using the application's ObjectContext I would query my tables like this:
var context = ObjectContextHelper.CurrentObjectContext;
var query = from c in context.ObjectSet .... etc
What I want would be to do something like this:
var context = ObjectContextHelper.CurrentObjectContext.Attach(WidgetObjectContext);
I know this won't work but that is the gist of what I am trying to accomplish. Hope this is clear enough. Thanks.
I don't think it is possible. ObjectContext creates entity connection which connects to metadata describing mapping and database. But you have to different sets of metadata - one for ASP.NET application and one for separate project. Simply you need two connection to work with these models => you need two ObjectContexts.
FYI: The previous answer was correct at the time of the answer. It is now possible to do this using the DbContext available in EF 4.1. The caveat is that you must use the code-first strategy in order to build your custom context. In other words, you won't be able to use EDMX files to accomplish this.

How does versioning work with Flex remote objects and AMF?

Suppose I use the [RemoteClass] tag to endow a custom Flex class with serialization intelligence.
What happens when I need to change my object (add a new field, remove a field, rename a field, etc)?
Is there a design pattern for handling this in an elegant way?
Your best bet is to do code generation against your backend classes to generation ActionScript counterparts for them. If you generate a base class with all of your object properties and then create a subclass for it which is never modified, you can still add custom code while regenerating only the parts of your class that change. Example:
java:
public class User {
public Long id;
public String firstName;
public String lastName;
}
as3:
public class UserBase {
public var id : Number;
public var firstName : String;
public var lastName : String;
}
[Bindable] [RemoteClass(...)]
public class User extends UserBase {
public function getFullName() : String {
return firstName + " " + lastName;
}
}
Check out the Granite Data Services project for Java -> AS3 code generation.
http://www.graniteds.org
Adding or removing generally works.
You'll get runtime warnings in your trace about properties either being missing or not found, but any data that is transferred and has a place to go will still get there. You need to keep this in mind while developing as not all your fields might have valid data.
Changing types, doesn't work so well and will often result in run time exceptions.
I like to use explicit data transfer objects and not to persist my actual data model that's used throughout the app. Then your translation from DTO->Model can take version differences into account.

Resources