Condividi tramite


Parte 5 - Strategie pratiche di condivisione del codice

In questa sezione vengono forniti esempi di come condividere il codice per scenari di applicazione comuni.

Livello dati

Il livello dati è costituito da un motore di archiviazione e metodi per leggere e scrivere informazioni. Per prestazioni, flessibilità e compatibilità multipiattaforma, è consigliabile usare il motore di database SQLite per le applicazioni multipiattaforma Xamarin. Viene eseguito su un'ampia gamma di piattaforme, tra cui Windows, Android, iOS e Mac.

SQLite

SQLite è un'implementazione del database open source. L'origine e la documentazione sono disponibili in SQLite.org. Il supporto di SQLite è disponibile in ogni piattaforma per dispositivi mobili:

  • iOS : integrato nel sistema operativo.
  • Android : integrato nel sistema operativo da Android 2.2 (livello API 10).
  • Windows: vedere l'estensione SQLite per piattaforma UWP (Universal Windows Platform).

Anche con il motore di database disponibile in tutte le piattaforme, i metodi nativi per accedere al database sono diversi. Sia iOS che Android offrono API predefinite per accedere a SQLite che possono essere usate da Xamarin.iOS o Xamarin.Android, ma l'uso dei metodi SDK nativi non offre alcuna possibilità di condividere codice (ad eccezione delle query SQL stesse, presupponendo che siano archiviate come stringhe). Per informazioni dettagliate sulle funzionalità native del database, cercare CoreData nella classe iOS o Android SQLiteOpenHelper . Poiché queste opzioni non sono multipiattaforma, non rientrano nell'ambito di questo documento.

ADO.NET

Sia il supporto System.Data di Xamarin.iOS che Xamarin.Android e Mono.Data.Sqlite (vedere la documentazione di Xamarin.iOS per altre informazioni). L'uso di questi spazi dei nomi consente di scrivere ADO.NET codice che funziona su entrambe le piattaforme. Modificare i riferimenti del progetto per includere System.Data.dll e Mono.Data.Sqlite.dll aggiungere queste istruzioni using al codice:

using System.Data;
using Mono.Data.Sqlite;

Il codice di esempio seguente funzionerà quindi:

string dbPath = Path.Combine (
        Environment.GetFolderPath (Environment.SpecialFolder.Personal),
        "items.db3");
bool exists = File.Exists (dbPath);
if (!exists)
    SqliteConnection.CreateFile (dbPath);
var connection = new SqliteConnection ("Data Source=" + dbPath);
connection.Open ();
if (!exists) {
    // This is the first time the app has run and/or that we need the DB.
    // Copy a "template" DB from your assets, or programmatically create one like this:
    var commands = new[]{
        "CREATE TABLE [Items] (Key ntext, Value ntext);",
        "INSERT INTO [Items] ([Key], [Value]) VALUES ('sample', 'text')"
    };
    foreach (var command in commands) {
        using (var c = connection.CreateCommand ()) {
            c.CommandText = command;
            c.ExecuteNonQuery ();
        }
    }
}
// use `connection`... here, we'll just append the contents to a TextView
using (var contents = connection.CreateCommand ()) {
    contents.CommandText = "SELECT [Key], [Value] from [Items]";
    var r = contents.ExecuteReader ();
    while (r.Read ())
        Console.Write("\n\tKey={0}; Value={1}",
                r ["Key"].ToString (),
                r ["Value"].ToString ());
}
connection.Close ();

Le implementazioni reali di ADO.NET sarebbero ovviamente suddivise tra metodi e classi diversi (questo esempio è solo a scopo dimostrativo).

SQLite-NET - ORM multipiattaforma

Un ORM (o Mapper relazionale a oggetti) tenta di semplificare l'archiviazione dei dati modellati nelle classi. Anziché scrivere manualmente query SQL create TABLes o SELECT, INSERT e DELETE estratte manualmente da campi e proprietà di classe, un ORM aggiunge automaticamente un livello di codice che esegue questa operazione. Usando la reflection per esaminare la struttura delle classi, un ORM può creare automaticamente tabelle e colonne che corrispondono a una classe e generare query per leggere e scrivere i dati. In questo modo il codice dell'applicazione può semplicemente inviare e recuperare istanze di oggetti all'ORM, che si occupa di tutte le operazioni SQL in background.

SQLite-NET funge da SEMPLICE ORM che consente di salvare e recuperare le classi in SQLite. Nasconde la complessità dell'accesso SQLite multipiattaforma con una combinazione di direttive del compilatore e altri trucchi.

Funzionalità di SQLite-NET:

  • Le tabelle vengono definite aggiungendo attributi alle classi Model.
  • Un'istanza di database è rappresentata da una sottoclasse di SQLiteConnection , la classe principale nella libreria SQLite-Net.
  • I dati possono essere inseriti, sottoposti a query ed eliminati tramite oggetti . Non sono necessarie istruzioni SQL (anche se è possibile scrivere istruzioni SQL se necessario).
  • Le query Linq di base possono essere eseguite sulle raccolte restituite da SQLite-NET.

Il codice sorgente e la documentazione per SQLite-NET sono disponibili in SQLite-Net su github ed è stato implementato in entrambi i case study. Di seguito è riportato un semplice esempio di codice SQLite-NET (dal case study Tasky Pro ).

Prima di tutto, la TodoItem classe usa gli attributi per definire un campo come chiave primaria del database:

public class TodoItem : IBusinessEntity
{
    public TodoItem () {}
    [PrimaryKey, AutoIncrement]
    public int ID { get; set; }
    public string Name { get; set; }
    public string Notes { get; set; }
    public bool Done { get; set; }
}

In questo modo è possibile creare una TodoItem tabella con la riga di codice seguente (e nessuna istruzione SQL) in un'istanza SQLiteConnection :

CreateTable<TodoItem> ();

I dati nella tabella possono anche essere modificati con altri metodi in SQLiteConnection (anche in questo caso, senza richiedere istruzioni SQL):

Insert (TodoItem); // 'task' is an instance with data populated in its properties
Update (TodoItem); // Primary Key field must be populated for Update to work
Table<TodoItem>.ToList(); // returns all rows in a collection

Per esempi completi, vedere il codice sorgente del case study.

Accesso ai file

L'accesso ai file è sicuramente una parte fondamentale di qualsiasi applicazione. Esempi comuni di file che potrebbero far parte di un'applicazione includono:

  • File di database SQLite.
  • Dati generati dall'utente (testo, immagini, audio, video).
  • Dati scaricati per la memorizzazione nella cache (immagini, file HTML o PDF).

System.IO l'accesso diretto

Sia Xamarin.iOS che Xamarin.Android consentono l'accesso al file system usando le classi nello spazio dei System.IO nomi .

Ogni piattaforma ha restrizioni di accesso diverse che devono essere prese in considerazione:

  • Le applicazioni iOS vengono eseguite in una sandbox con accesso al file system molto limitato. Apple determina ulteriormente come usare il file system specificando determinati percorsi di cui è stato eseguito il backup (e altri che non lo sono). Per altri dettagli, vedere la guida Uso del file system in Xamarin.iOS .
  • Android limita anche l'accesso a determinate directory correlate all'applicazione, ma supporta anche supporti esterni ( ad esempio. Schede SD) e accesso ai dati condivisi.
  • Windows Phone 8 (Silverlight) non consente l'accesso diretto ai file. I file possono essere modificati solo usando IsolatedStorage.
  • I progetti WinRT e Windows 10 UWP di Windows 8.1 offrono solo operazioni di file asincrone tramite Windows.Storage API, che sono diverse dalle altre piattaforme.

Esempio per iOS e Android

Di seguito è riportato un esempio semplice che scrive e legge un file di testo. L'uso Environment.GetFolderPath consente di eseguire lo stesso codice in iOS e Android, che restituiscono una directory valida in base alle convenzioni del file system.

string filePath = Path.Combine (
        Environment.GetFolderPath (Environment.SpecialFolder.Personal),
        "MyFile.txt");
System.IO.File.WriteAllText (filePath, "Contents of text file");
Console.WriteLine (System.IO.File.ReadAllText (filePath));

Per altre informazioni sulla funzionalità del file system specifico di iOS, vedere il documento Xamarin.iOS Uso del file system . Quando si scrive codice di accesso ai file multipiattaforma, tenere presente che alcuni file system fanno distinzione tra maiuscole e minuscole e hanno separatori di directory diversi. È consigliabile usare sempre la stessa combinazione di maiuscole e minuscole per i nomi file e il metodo durante la Path.Combine() costruzione di percorsi di file o directory.

Windows.Storage per Windows 8 e Windows 10

Il libroCreazione di app per dispositivi mobili con Xamarin.Forms capitolo 20. Async e File I/O includono esempi per Windows 8.1 e Windows 10.

L'uso di un DependencyService è possibile leggere e file su queste piattaforme usando le API supportate:

StorageFolder localFolder = ApplicationData.Current.LocalFolder;
IStorageFile storageFile = await localFolder.CreateFileAsync("MyFile.txt",
                                        CreationCollisionOption.ReplaceExisting);
await FileIO.WriteTextAsync(storageFile, "Contents of text file");

Per altri dettagli, vedere il capitolo 20 del libro.

Archiviazione isolata in Windows Phone 7 & 8 (Silverlight)

L'archiviazione isolata è un'API comune per salvare e caricare file in tutte le piattaforme iOS, Android e Windows Phone precedenti.

È il meccanismo predefinito per l'accesso ai file in Windows Phone (Silverlight) implementato in Xamarin.iOS e Xamarin.Android per consentire la scrittura di codice comune di accesso ai file. È possibile fare riferimento alla System.IO.IsolatedStorage classe in tutte e tre le piattaforme di un progetto condiviso.

Per altre informazioni, vedere Panoramica dello spazio di memorizzazione isolato per Windows Phone .

Le API di archiviazione isolata non sono disponibili nelle librerie di classi portabili. Un'alternativa per pcl è pclStorage NuGet

Accesso ai file multipiattaforma nei PCL

È disponibile anche un NuGet compatibile con PCL , PCLStorage , che consente l'accesso ai file multipiattaforma per le piattaforme supportate da Xamarin e le API Windows più recenti.

Operazioni per la rete

La maggior parte delle applicazioni per dispositivi mobili avrà un componente di rete, ad esempio:

  • Download di immagini, video e audio (ad esempio anteprime, foto, musica).
  • Download di documenti (ad esempio. HTML, PDF).
  • Caricamento dei dati utente (ad esempio foto o testo).
  • Accesso a servizi Web o API di terze parti (inclusi SOAP, XML o JSON).

.NET Framework offre alcune classi diverse per l'accesso alle risorse di rete: HttpClient, WebCliente HttpWebRequest.

HttpClient

La HttpClient classe nello spazio dei System.Net.Http nomi è disponibile in Xamarin.iOS, Xamarin.Android e nella maggior parte delle piattaforme Windows. È disponibile un NuGet della libreria client HTTP Microsoft che può essere usato per inserire questa API in librerie di classi portabili (e Windows Phone 8 Silverlight).

var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://xamarin.com");
var response = await myClient.SendAsync(request);

WebClient

La WebClient classe fornisce una semplice API per recuperare i dati remoti dai server remoti.

piattaforma UWP (Universal Windows Platform) le operazioni devono essere asincrone, anche se Xamarin.iOS e Xamarin.Android supportano operazioni sincrone (che possono essere eseguite sui thread in background).

Il codice per una semplice operazione asincrona WebClient è:

var webClient = new WebClient ();
webClient.DownloadStringCompleted += (sender, e) =>
{
    var resultString = e.Result;
    // do something with downloaded string, do UI interaction on main thread
};
webClient.Encoding = System.Text.Encoding.UTF8;
webClient.DownloadStringAsync (new Uri ("http://some-server.com/file.xml"));

WebClient dispone DownloadFileCompleted inoltre di e DownloadFileAsync per il recupero di dati binari.

HttpWebRequest

HttpWebRequest offre una maggiore personalizzazione di WebClient e di conseguenza richiede più codice da usare.

Il codice per una semplice operazione sincrona HttpWebRequest è:

var request = HttpWebRequest.Create(@"http://some-server.com/file.xml ");
request.ContentType = "text/xml";
request.Method = "GET";
using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
{
    if (response.StatusCode != HttpStatusCode.OK)
        Console.WriteLine("Error fetching data. Server returned status code: {0}", response.StatusCode);
    using (StreamReader reader = new StreamReader(response.GetResponseStream()))
    {
        var content = reader.ReadToEnd();
        // do something with downloaded string, do UI interaction on main thread
    }
}

Nella documentazione dei servizi Web è disponibile un esempio.

Raggiungibilità

I dispositivi mobili operano in diverse condizioni di rete, dalle connessioni Wi-Fi o 4G veloci alle aree di ricezione scadenti e ai collegamenti dati EDGE lenti. Per questo motivo, è consigliabile rilevare se la rete è disponibile e, in tal caso, quale tipo di rete è disponibile, prima di tentare di connettersi ai server remoti.

Le azioni che possono essere eseguite da un'app per dispositivi mobili in queste situazioni includono:

  • Se la rete non è disponibile, avvisare l'utente. Se l'hanno disabilitata manualmente (ad esempio, Modalità aereo o disattivazione del Wi-Fi, quindi possono risolvere il problema.
  • Se la connessione è 3G, le applicazioni possono comportarsi in modo diverso (ad esempio, Apple non consente il download delle app di dimensioni superiori a 20 Mb su 3G). Le applicazioni possono usare queste informazioni per avvisare l'utente di tempi di download eccessivi durante il recupero di file di grandi dimensioni.
  • Anche se la rete è disponibile, è consigliabile verificare la connettività con il server di destinazione prima di avviare altre richieste. In questo modo si impedisce che le operazioni di rete dell'app si ripetano ripetutamente e consentano anche la visualizzazione di un messaggio di errore più informativo all'utente.

WebServices

Vedere la documentazione sull'uso dei servizi Web, che illustra l'accesso agli endpoint REST, SOAP e WCF con Xamarin.iOS. È possibile creare richieste di servizio Web e analizzare le risposte, tuttavia sono disponibili librerie per semplificare questa operazione, tra cui Azure, RestSharp e ServiceStack. È anche possibile accedere alle operazioni WCF di base nelle app Xamarin.

Azure

Microsoft Azure è una piattaforma cloud che offre un'ampia gamma di servizi per le app per dispositivi mobili, tra cui l'archiviazione e la sincronizzazione dei dati e le notifiche push.

Visita azure.microsoft.com per provarlo gratuitamente.

RestSharp

RestSharp è una libreria .NET che può essere inclusa nelle applicazioni per dispositivi mobili per fornire un client REST che semplifica l'accesso ai servizi Web. Consente di fornire un'API semplice per richiedere i dati e analizzare la risposta REST. RestSharp può essere utile

Il sito Web RestSharp contiene la documentazione su come implementare un client REST usando RestSharp. RestSharp fornisce esempi di Xamarin.iOS e Xamarin.Android in github.

Nella documentazione dei servizi Web è disponibile anche un frammento di codice Xamarin.iOS.

ServiceStack

A differenza di RestSharp, ServiceStack è sia una soluzione lato server per ospitare un servizio Web che una libreria client che può essere implementata nelle applicazioni per dispositivi mobili per accedere a tali servizi.

Il sito Web ServiceStack illustra lo scopo del progetto e i collegamenti a documenti ed esempi di codice. Gli esempi includono un'implementazione completa lato server di un servizio Web e diverse applicazioni lato client che possono accedervi.

WCF

Gli strumenti Xamarin consentono di utilizzare alcuni servizi Windows Communication Foundation (WCF). In generale, Xamarin supporta lo stesso subset lato client di WCF fornito con il runtime di Silverlight. Sono incluse le implementazioni di codifica e protocollo più comuni di WCF: messaggi SOAP codificati in testo sul protocollo di trasporto HTTP tramite .BasicHttpBinding

A causa delle dimensioni e della complessità del framework WCF, potrebbero esserci implementazioni del servizio correnti e future che non rientrano nell'ambito supportato dal dominio client-subset di Xamarin. Inoltre, il supporto wcf richiede l'uso di strumenti disponibili solo in un ambiente Windows per generare il proxy.

Threading

La velocità di risposta delle applicazioni è importante per le applicazioni per dispositivi mobili: gli utenti si aspettano che le applicazioni vengano caricate ed eseguite rapidamente. Verrà visualizzata una schermata "bloccata" che interrompe l'accettazione dell'input dell'utente per indicare che l'applicazione si è arrestata in modo anomalo, quindi è importante non collegare il thread dell'interfaccia utente con chiamate di blocco a esecuzione prolungata, ad esempio le richieste di rete o le operazioni locali lente, ad esempio la decompressione di un file. In particolare, il processo di avvio non deve contenere attività a esecuzione prolungata. Tutte le piattaforme mobili uccideranno un'app che richiede troppo tempo per il caricamento.

Ciò significa che l'interfaccia utente deve implementare un "indicatore di stato" o un'interfaccia utente "utilizzabile" rapida da visualizzare e attività asincrone per eseguire operazioni in background. L'esecuzione di attività in background richiede l'uso di thread, il che significa che le attività in background richiedono un modo per comunicare con il thread principale per indicare lo stato di avanzamento o quando sono state completate.

Libreria di attività parallele

Le attività create con la libreria di attività parallele possono essere eseguite in modo asincrono e restituire il thread chiamante, rendendole molto utili per attivare operazioni a esecuzione prolungata senza bloccare l'interfaccia utente.

Un'operazione di attività parallela semplice potrebbe essere simile alla seguente:

using System.Threading.Tasks;
void MainThreadMethod ()
{
    Task.Factory.StartNew (() => wc.DownloadString ("http://...")).ContinueWith (
        t => label.Text = t.Result, TaskScheduler.FromCurrentSynchronizationContext()
    );
}

La chiave consiste TaskScheduler.FromCurrentSynchronizationContext() nel riutilizzare SynchronizationContext.Current del thread che chiama il metodo (in questo caso il thread principale che esegue MainThreadMethod) come modo per effettuare il marshalling delle chiamate a tale thread. Ciò significa che se il metodo viene chiamato nel thread dell'interfaccia utente, eseguirà di nuovo l'operazione ContinueWith nel thread dell'interfaccia utente.

Se il codice avvia attività da altri thread, usare il modello seguente per creare un riferimento al thread dell'interfaccia utente e l'attività può comunque richiamarla:

static Context uiContext = TaskScheduler.FromCurrentSynchronizationContext();

Chiamata al thread dell'interfaccia utente

Per il codice che non usa la libreria di attività parallele, ogni piattaforma ha la propria sintassi per il marshalling delle operazioni nel thread dell'interfaccia utente:

  • iOS : owner.BeginInvokeOnMainThread(new NSAction(action))
  • Android : owner.RunOnUiThread(action)
  • Xamarin.Forms : Device.BeginInvokeOnMainThread(action)
  • Windows : Deployment.Current.Dispatcher.BeginInvoke(action)

Sia la sintassi iOS che Android richiedono che sia disponibile una classe "context", il che significa che il codice deve passare questo oggetto in qualsiasi metodo che richieda un callback nel thread dell'interfaccia utente.

Per effettuare chiamate di thread dell'interfaccia utente nel codice condiviso, seguire l'esempio IDispatchOnUIThread (per gentile concessione di @follesoe). Dichiarare e programmare in un'interfaccia IDispatchOnUIThread nel codice condiviso e quindi implementare le classi specifiche della piattaforma, come illustrato di seguito:

// program to the interface in shared code
public interface IDispatchOnUIThread {
    void Invoke (Action action);
}
// iOS
public class DispatchAdapter : IDispatchOnUIThread {
    public readonly NSObject owner;
    public DispatchAdapter (NSObject owner) {
        this.owner = owner;
    }
    public void Invoke (Action action) {
        owner.BeginInvokeOnMainThread(new NSAction(action));
    }
}
// Android
public class DispatchAdapter : IDispatchOnUIThread {
    public readonly Activity owner;
    public DispatchAdapter (Activity owner) {
        this.owner = owner;
    }
    public void Invoke (Action action) {
        owner.RunOnUiThread(action);
    }
}
// WP7
public class DispatchAdapter : IDispatchOnUIThread {
    public void Invoke (Action action) {
        Deployment.Current.Dispatcher.BeginInvoke(action);
    }
}

Gli sviluppatori di Xamarin.Forms devono usare Device.BeginInvokeOnMainThread nel codice comune (Progetti condivisi o PCL).

Funzionalità della piattaforma e del dispositivo e riduzione delle prestazioni

Altri esempi specifici di gestione delle diverse funzionalità sono disponibili nella documentazione relativa alle funzionalità della piattaforma. Gestisce il rilevamento di diverse funzionalità e come ridurre normalmente un'applicazione per offrire un'esperienza utente ottimale, anche quando l'app non può operare al massimo.