CommunityToolkit.Maui.Core.Views.MauiPopup throws System.ObjectDisposedException on Close() - maui

The following code results in a System.ObjectDisposedException on the line Close(result); in BarcodeScannerPopup.xaml.cs when a barcode is detected by the CameraBarcodeReaderView.
The detected barcode is correctly displayed in the label in BarcodePage.
Any idea why this exception is thrown?
BarcodePage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
x:Class="MyApp.BarcodePage"
x:DataType="vm:BarcodeViewModel"
xmlns:vm="clr-namespace:MyApp.ViewModels"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui">
<VerticalStackLayout
Margin="20">
<Label
Text="{Binding Barcode}"/>
<Button
Command="{Binding ScanBarcodeClickCommand}"
Text="Scan barcode"/>
</VerticalStackLayout>
</ContentPage>
BarcodePage.xaml.cs
using MyApp.ViewModels;
namespace MyApp.Views;
public partial class BarcodePage : ContentPage
{
public BarcodePage(BarcodeViewModel viewModel)
{
InitializeComponent();
BindingContext = viewModel;
}
}
BarcodeScannerPopup.xaml
<?xml version="1.0" encoding="utf-8" ?>
<toolkit:Popup
x:Class="MyApp.BarcodeScannerPopup"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:zxing="clr-namespace:ZXing.Net.Maui.Controls;assembly=ZXing.Net.MAUI"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui">
<VerticalStackLayout
Margin="20">
<Label
Text="Add a barcode by scanning it."
VerticalOptions="Center"
HorizontalOptions="Center"/>
<zxing:CameraBarcodeReaderView
x:Name="barcodeReader"
BarcodesDetected="BarcodesDetected"
HeightRequest="300"
IsDetecting="True"
Margin="5"
WidthRequest="300"/>
</VerticalStackLayout>
</toolkit:Popup>
BarcodeScannerPopup.xaml.cs
using CommunityToolkit.Maui.Views;
using ZXing.Net.Maui;
namespace MyApp.Views;
public partial class BarcodeScannerPopup : Popup
{
public BarcodeScannerPopup()
{
InitializeComponent();
barcodeReader.Options = new BarcodeReaderOptions
{
AutoRotate = true,
Multiple = false
};
}
private void BarcodesDetected(object sender, BarcodeDetectionEventArgs e)
{
var result = e.Results[0].Value;
Close(result);
}
}
BarcodeViewModel.cs
using CommunityToolkit.Maui.Views;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using MyApp.Views;
namespace MyApp.ViewModels;
public partial class BarcodeViewModel : ObservableObject
{
[ObservableProperty]
private string _barcode;
private BarcodeScannerPopup _popup = new BarcodeScannerPopup();
[RelayCommand]
public async Task ScanBarcodeClick()
{
Barcode = (string)await Application.Current.MainPage.ShowPopupAsync(_popup);
}
}

#ToolmakerSteve was right. The CameraBarcodeReaderView continues sending data which results in the System.ObjectDisposedException. setting the IsDetecting property to false prevents this. As this was a PoC I didn't add a proper null check on e, so I've added as a good coding practise in this example.
BarcodeScannerPopup.xaml.cs
using CommunityToolkit.Maui.Views;
using ZXing.Net.Maui;
namespace MyApp.Views;
public partial class BarcodeScannerPopup : Popup
{
public BarcodeScannerPopup()
{
InitializeComponent();
barcodeReader.Options = new BarcodeReaderOptions
{
AutoRotate = true,
Multiple = false
};
}
private void BarcodesDetected(object sender, BarcodeDetectionEventArgs e)
{
var result = e?.Results?.Any() == true
? e.Results[0].Value
: string.Empty;
IsDetecting = false;
Close(result);
}
}

Related

Collectionview SelectionChangeCommand doesn´t work with CommunityToolkit.Mvvm .NET MAUI

I have a simple project using Community Toolkit Mvvm tool and a collectionview. The issue is that the SelectionChangeCommand of the CollectionView doesn´t fired when I select an element of the collection. I created this project because on another more complex project the error is that it can't find the binding command on the viewmodel. I know that the connection between the view and viewmodel is working because the collectionview is filling with elements on the viewmodel and also I am able to change the visibility of the border through the binded property.
Sample project: https://github.com/luis95gr/MvvmError.App
Code
LoginPage (first page with collectionview)
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="MvvmError.Views.LoginPage"
xmlns:models="clr-namespace:MvvmError.Models"
Title="LoginPage">
<RefreshView VerticalOptions="FillAndExpand">
<Border BackgroundColor="Red" IsVisible="{Binding Visible}" Stroke="Red" HorizontalOptions="FillAndExpand" Padding="0,0" Margin="0,0,0,0" VerticalOptions="FillAndExpand">
<Border.StrokeShape>
<RoundRectangle CornerRadius="40,0,0,0" />
</Border.StrokeShape>
<CollectionView Background="Transparent" ItemsSource="{Binding Messages}" SelectionMode="Single" SelectedItem="{Binding MessageSelected}" SelectionChangedCommand="{Binding MessageSelectedCommand}">
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:MessagesMessages">
<Grid RowDefinitions="100*" ColumnDefinitions="100*">
<Label Grid.Row="0" Text="{Binding Asunto}" />
</Grid>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
</Border>
</RefreshView>
LoginPageViewModel
public partial class LoginPageViewModel : ObservableObject
{
[ObservableProperty]
private MessagesMessages messageSelected;
[ObservableProperty]
private bool visible;
public ObservableCollection<MessagesMessages> Messages { get; } = new();
public LoginPageViewModel()
{
Visible = true;
GetMessages();
}
[RelayCommand]
async void MessageSelectedCommand()
{
var snackbar = Snackbar.Make("Hola");
await snackbar.Show();
}
private void GetMessages()
{
Messages.Add(new MessagesMessages
{
Asunto = "Hola"
});
Messages.Add(new MessagesMessages
{
Asunto = "Hola1"
});
Messages.Add(new MessagesMessages
{
Asunto = "Hola2"
});
Messages.Add(new MessagesMessages
{
Asunto = "Hola3"
});
}
}
LoginPage.xaml.cs
public partial class LoginPage : ContentPage
{
public LoginPage(LoginPageViewModel loginPageViewModel)
{
InitializeComponent();
BindingContext= loginPageViewModel;
}
}
MauiProgram.cs
builder.Services.AddTransient<LoginPage>();
builder.Services.AddSingleton<ViewModels.LoginPageViewModel>();
you are not following the naming convention
The name of the generated command will be created based on the method
name. The generator will use the method name and append "Command" at
the end, and it will strip the "On" prefix, if present. Additionally,
for asynchronous methods, the "Async" suffix is also stripped before
"Command" is appeneded.
to generate a command named MessageSelectedCommand your method should look like
[RelayCommand]
async void MessageSelected()

MAUI: Listview cannot be refreshed automatically

There are 2 elements binding data in my test app, one is Button, another is Listview. But Listview cannnot be refreshed automatically. But if I disable ButtonText = $"It took {totalSecs} seconds" in ViewModel, Listview can be refreshed automatically. Any ideas? TIA.
Here is the Xaml file.
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:test"
x:Class="test.MainPage">
<ContentPage.BindingContext>
<local:PurchasesInvoiceMasterViewModel />
</ContentPage.BindingContext>
<ScrollView>
<VerticalStackLayout
Spacing="25"
Padding="30,0"
VerticalOptions="Start">
<Button
x:Name="PurchasesInvoiceList"
Text="{Binding ButtonText}"
Command="{Binding AddListCommand}"
SemanticProperties.Hint="Get Purchases Invoice List"
HorizontalOptions="Center" />
<ListView x:Name="PurchasesInvoiceMasterObjectView" ItemsSource="{Binding PurchasesInvoiceMasterObject}" HeightRequest="500">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding purchases_invoice_id}"
Detail="{Binding purchases_invoice_date }" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</VerticalStackLayout>
</ScrollView>
Here is ViewModel.
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Xml.Linq;
namespace test
{
class PurchasesInvoiceMasterViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public ICommand AddListCommand { get; set; }
private string _buttonText = "Purchases Invoice List";
public string ButtonText
{
get => _buttonText;
set
{
if (_buttonText != value)
{
_buttonText = value;
OnPropertyChanged("");
}
}
}
private ObservableCollection<PurchasesInvoiceMaster> _purchasesInvoiceMasterObject = new();
public ObservableCollection<PurchasesInvoiceMaster> PurchasesInvoiceMasterObject
{
get => _purchasesInvoiceMasterObject;
set
{
if (_purchasesInvoiceMasterObject != value)
{
_purchasesInvoiceMasterObject = value;
OnPropertyChanged("");
}
}
}
public PurchasesInvoiceMasterViewModel()
{
AddListCommand = new Command(() =>
{
double statrtSecs = DateTime.Now.TimeOfDay.TotalSeconds;
string purchasesInvoiceMaster = "test.PurchasesInvoiceMaster.xml";
using (Stream streamPurchasesInvoiceMaster = this.GetType().Assembly.
GetManifestResourceStream(purchasesInvoiceMaster))
{
using (var readerPurchasesInvoiceMaster = new System.IO.StreamReader(streamPurchasesInvoiceMaster))
{
var xmlpurchasesInvoiceMaster = XDocument.Load(readerPurchasesInvoiceMaster);
foreach (XElement xmmd in xmlpurchasesInvoiceMaster.Descendants("curtemp01"))
{
_purchasesInvoiceMasterObject.Add(new PurchasesInvoiceMaster
{
purchases_invoice_id = xmmd.Element("purchases_invoice_id").Value,
purchases_invoice_date = xmmd.Element("purchases_invoice_date").Value,
});
}
}
}
double endSecs = DateTime.Now.TimeOfDay.TotalSeconds;
float totalSecs = (float)(endSecs - statrtSecs);
ButtonText = $"It took {totalSecs} seconds";
});
}
public void OnPropertyChanged([CallerMemberName] string name = "") =>
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
}
}

Xamarin forms TabbedPage View Model called multiple time

I have implemented Tabbedpage using ViewModel but my ViewModel constructor call 4 times because I create 4 tabs, I also used prism for ViewModel binding.
Below is a design file
<?xml version="1.0" encoding="UTF-8"?>
<TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
xmlns:material="clr-namespace:XF.Material.Forms.UI;assembly=XF.Material"
xmlns:ffimageloading="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
xmlns:ffTransformations="clr-namespace:FFImageLoading.Transformations;assembly=FFImageLoading.Transformations"
prism:ViewModelLocator.AutowireViewModel="True"
xmlns:ios="clr-namespace:Xamarin.Forms.PlatformConfiguration.iOSSpecific;assembly=Xamarin.Forms.Core"
xmlns:extended="clr-namespace:Xamarin.Forms.Extended;assembly=Xamarin.Forms.Extended.InfiniteScrolling"
xmlns:customcontrols="clr-namespace:QuranicQuizzes.CustomControls"
xmlns:local="clr-namespace:QuranicQuizzes.Views" NavigationPage.HasNavigationBar="True"
x:Class="QuranicQuizzes.Views.DashboardPage">
<NavigationPage.TitleView>
<StackLayout Orientation="Horizontal" HorizontalOptions="FillAndExpand">
<Label Text="Dashboard" TextColor="White" HorizontalTextAlignment="Center" HorizontalOptions="CenterAndExpand" VerticalTextAlignment="Center" FontFamily="{StaticResource QuranFontBold}" FontSize="Medium" />
<StackLayout Orientation="Horizontal">
<material:MaterialMenuButton x:Name="Menus" ButtonType="Text" Image="list" TintColor="White" BackgroundColor="Transparent" CornerRadius="24" Choices="{Binding Actions}" MenuSelected="MaterialMenuButton_MenuSelected" />
</StackLayout>
</StackLayout>
</NavigationPage.TitleView>
<local:HomeTabPage/>
<local:QuizzesTabPage/>
<local:LiveGameTabPage/>
<local:AssignmentTabPage/>
</TabbedPage>
Below is my code
public partial class DashboardPage : TabbedPage
{
private DashboardPageViewModel vm;
public DashboardPage()
{
try
{
InitializeComponent();
vm = BindingContext as DashboardPageViewModel;
}
catch (Exception ex)
{
}
}
}
Below is my ViewModel
public class DashboardPageViewModel : ViewModelBase
{
INavigationService _navigationService;
IClientAPI _clientAPI;
Dashboards dashboard;
public DashboardPageViewModel(INavigationService navigationService, IClientAPI clientAPI) : base(navigationService)
{
_navigationService = navigationService;
_clientAPI = clientAPI;
if (CrossConnectivity.Current.IsConnected)
{
var StartDate = DateTime.Now.AddDays(-7).ToString("yyyy-MM-dd");
var Enddate = DateTime.Now.ToString("yyyy-MM-dd");
if (dashboard == null)
{
dashboard = new Dashboards();
getDashboardData(StartDate, Enddate);
}
}
}
}
I see what you're trying to do. You want to initialise your vm instance so that you can access you vm from your view.
Instead of doing this:
vm = BindingContext as DashboardPageViewModel;
what we can do is change the type of the existing BindingContext property by doing this:
public partial class DashboardPage
{
new DashboardPageViewModel BindingContext
{
get => (DashboardPageViewModel) base.BindingContext;
set => base.BindingContext = value;
}
public DashboardPage()
{
InitializeComponent();
}
}
now you can just access BindingContext.DoSomething because its type is now DashboardPageViewModel.
Now that's sorted out, your viewmodel should not be being called 4 times! Something is wrong here. Here is a checklist of things to do that may be causing the constructor being called 4 times as not a lot more info was provided.
Try removing <NavigationPage.TitleView> segment.
Make sure you are navigating to DashboardPage.
Make sure that each individual TabbedPage has it's own viewmodel.
Try removing prism:ViewModelLocator.AutowireViewModel="True"and manually adding the viewmodel to the TabbedPage.
Finally constructors should be able to run very fast and should only be used for assigning variables or instantiation or very quick operations. What you could maybe do is separate the code in your VM:
public class DashboardPageViewModel : ViewModelBase
{
IClientAPI _clientAPI;
Dashboards dashboard;
public DashboardPageViewModel(INavigationService navigationService, IClientAPI clientAPI) : base(navigationService)
{
_clientAPI = clientAPI;
}
public void Init()
{
if (CrossConnectivity.Current.IsConnected)
{
var StartDate = DateTime.Now.AddDays(-7).ToString("yyyy-MM-dd");
var Enddate = DateTime.Now.ToString("yyyy-MM-dd");
if (dashboard == null)
{
dashboard = new Dashboards();
getDashboardData(StartDate, Enddate);
}
}
}
}
and then in your view you could add this method:
protected override void OnBindingContextChanged()
{
base.OnBindingContextChanged();
if(BindingContext == null)
{
return;
}
BindingContext.Init();
}
I hope this really helps you.
NB: All this code was written on the fly and never compiled, there may be some errors.

How to call TapGestureRecognizer from ViewModel

I'm trying to implement TapGestureRecognizer which will be called in ViewModel (xaml.cs) not in View class...
Here is sample code in xaml file: (IrrigNetPage.xaml)
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:i18n="clr-namespace:agroNet.AppResource;assembly=agroNet"
xmlns:viewModels="clr-namespace:agroNet.ViewModel"
x:Class="agroNet.View.IrrigNetPage"
BackgroundColor="#EBEBEB">
<Grid>
<Grid.GestureRecognizers>
<TapGestureRecognizer Tapped="HideListOnTap"/>
</Grid.GestureRecognizers>
</Grid>
I implemented HideListOnTap in xaml.cs page (view) like this: (IrrigNetPage.xaml.cs)
int visibility = 1;
private void HideListOnTap(object sender, EventArgs e)
{
visibility++;
if ((visibility % 2) == 0)
{
IrrigList.IsVisible = false;
}
else
{
IrrigList.IsVisible = true;
}
}
It's working fine, but how to do the same thing usin ViewModel?
(How to bind Gesture recognizer from (IrrigNetPage.xaml) with HideListOnTap in IrrigNetViewModel )
Use a Command whenever you want to handle some event in the ViewModel. Without passing any arguments the code would look like as follows
<!-- in IrrigNetPage.xaml -->
<TapGestureRecognizer Command="{Binding HideListOnTapCommand}"/>
And in the ViewModel IrrigNetPageViewModel.cs
public ICommand HideListOnTapCommand { get; }
public IrrigNetPageViewModel()
{
HideListOnTapCommand = new Command(HideListOnTap);
// if HideListOnTap is async create your command like this
// HideListOnTapCommand = new Command(async() => await HideListOnTap());
}
private void HideListOnTap()
{
// do something
}

Xamarin Forms with Prism - Problem with using WebService

Can anyone help me? I created simply project Xamarin Forms with Prism i VS2017 on Android (screen). I used Prism Template Pack. I would like connect project with my WebService. here is a link to screen of all project
I have two projects PrismCoursApp and PrismCoursApp.Droid. First project contains SecondPageViewModel.cs where I try use connected WebService (wsMES) but I can't add namespace with PrismCoursApp.Droid.
The namespace of project PrismCourseApp.Android is PrismCourseApp.Droid and
PrismCourseApp.Android depends on PrismCourseApp.
I could add reference to Web service only in PrismCoursApp.Android project but I would like to use it in SecondPageViewModel.cs in PrismCourseApp.
Can someone tell me what I'm doing wrong?
Thanks
SecondPageViewModel.cs
using Prism.Commands;
using Prism.Mvvm;
using Prism.Navigation;
using System;
using System.Collections.Generic;
using System.Linq;
using PrismCourseApp.Models;
using System.Collections.ObjectModel;
namespace PrismCourseApp.ViewModels
{
public class SecondPageViewModel : BindableBase, INavigationAware
{
//zmienna do WebService
//wsMES.WSwitoMES ws = new wsMES.WSwitoMES();
private string _title;
public string Title
{
get { return _title; }
set { SetProperty(ref _title, value); }
}
private string _UserCode;
public string UserCode
{
get { return _UserCode; }
set { SetProperty(ref _UserCode, value); }
}
private string _LokalizCode;
public string LokalizCode
{
get { return _LokalizCode; }
set { SetProperty(ref _LokalizCode, value); }
}
public SecondPageViewModel()
{
UserCode = AppStateTest.User;
LokalizCode = AppStateTest.CurrentCode;
Title = "Użytkownik/Lokalizacja";
}
public void OnNavigatedFrom(INavigationParameters parameters)
{
}
public void OnNavigatedTo(INavigationParameters parameters)
{
if (parameters.ContainsKey("par1"))
{
string par1 = (string)parameters["par1"];
string par2 = (string)parameters["par2"];
}
}
public void OnNavigatingTo(INavigationParameters parameters)
{
}
}
}
SecondPage.axml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:prism="clr-namespace:Prism.Mvvm;assembly=Prism.Forms"
prism:ViewModelLocator.AutowireViewModel="True"
x:Class="PrismCourseApp.Views.SecondPage"
BackgroundColor="White"
Title="{Binding Title}"
xmlns:b="clr-namespace:Prism.Behaviors;assembly=Prism.Forms"
xmlns:c="clr-namespace:PrismCourseApp.Converters;assembly=PrismCourseApp">
<ContentPage.Resources>
<ResourceDictionary>
<!--<c:ItemTappedEventArgsConverter x:Key="itemTappedEventArgsConverter" />-->
</ResourceDictionary>
</ContentPage.Resources>
<StackLayout
Spacing="20">
<Label
Text="Zalogowany użytkownik:"
TextColor="Gray"/>
<Label
Text="{Binding UserCode}"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<Label
Text="Lokalizacja:"
TextColor="Gray"/>
<Label
Text="{Binding LokalizCode}"
FontSize="Large"
HorizontalOptions="Center"
VerticalOptions="CenterAndExpand" />
<ListView
x:Name="lstView">
<!--ItemsSource="{Binding MyDatas}">-->
<!--<ListView.Behaviors>
<b:EventToCommandBehavior EventName="ItemTapped"
Command="{Binding ItemTappedCommand}"
EventArgsConverter="{StaticResource itemTappedEventArgsConverter}" />
</ListView.Behaviors>-->
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding name}" Detail="{Binding comment}" />
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
SecondPage.axml.cs
using Xamarin.Forms;
using PrismCourseApp.Models;
using System.Collections.ObjectModel;
namespace PrismCourseApp.Views
{
public partial class SecondPage : ContentPage
{
//Elementy do ListView (klasa MyDate w PrismCourseApp)
private ObservableCollection<MyDate> MyDatas { get; set; }
public SecondPage()
{
InitializeComponent();
MyDatas = new ObservableCollection<MyDate>();
lstView.ItemsSource = MyDatas;
for (int i = 0; i < 30; i++)
{
MyDatas.Add(new MyDate
{
name = "Pozycja " + (i+1).ToString(),
comment = "Miejsce na szczegóły " + (i+1).ToString()
});
}
}
}
}
MainActivity.cs in Android Project
using Android.App;
using Android.Content.PM;
using Android.OS;
using Prism;
using Prism.Ioc;
namespace PrismCourseApp.Droid
{
[Activity(Label = "PrismCourseApp", Icon = "#drawable/ic_launcher", Theme = "#style/MainTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)]
public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity
{
protected override void OnCreate(Bundle bundle)
{
TabLayoutResource = Resource.Layout.Tabbar;
ToolbarResource = Resource.Layout.Toolbar;
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication(new App(new AndroidInitializer()));
}
}
public class AndroidInitializer : IPlatformInitializer
{
public void RegisterTypes(IContainerRegistry container)
{
// Register any platform specific implementations
}
}
}
Summarising the discussion in the comments above:
One should not make a web service dependent upon something specific to one of many client platforms. It's preferable to put the service's interface between the server and the part of the client that's shared between the different client implementations.
Say you have a mobile app for Android and IOS. Then you'll have two projects MyApp.Droid and MyApp.IOS for the respective client-specific implementations. Also, there's a project that both of them reference, and that (hopefully) contains most of your app's client-side logic: MyApp.Logic.
Now for the server: you have the MyApp.Server project that implements the service. If you need to define interfaces to communicate between the app and the service (WCF comes to mind), you define a project referenced by both the client-side logic (MyApp.Logic) and the server implementation (MyApp.Server): MyApp.Interface.
MyApp.Droid & MyApp.IOS -ref-> MyApp.Logic -ref-> MyApp.Interface <-ref- MyApp.Server