Login Login
MORE

WIDGETS

Widgets

Wanted articles
Who is online?
Article tools

CSharp:Android App - primi step

From Aino Wiki

Jump to: navigation, search

Cenni teorici

Per sviluppare applicazioni destinate ai dispositivi Android usando Microsoft Visual Studio, si usa la tecnologia Xamarin (dall'omonima azienda acquistata da Microsoft nel 2016).
Lo sviluppo di un'applicazione con Xamarin può avvenire utilizzando due approcci differenti chiamati Xamarin Native e Xamarin.Forms.
Con il termine Xamarin Native, si indica lo sviluppo di codice specifico per una singola piattaforma, Android (Xamarin.Android) o alternativamente iOS (Xamarin.iOS), entrambe richiedono specifiche librerie quindi specifici SDK.
Xamarin.Android e Xamarin.iOS sono entrambi progettati sulla base di Mono, una implementazione open source di Microsoft .NET Framework basata sugli standard aperti .NET ECMA.
I due approcci Xamarin Native e Xamarin.Forms però non sono mutuamente esclusivi.
Un'applicazione è realizzabile in gran parte utilizzando Xamarin.Forms ed è possibile intervenire solo in specifici punti con Xamarin Native dove richiesto. In questo modo si possono ottenere i vantaggi di ciascun approccio.

Xamarin Native

In Android il compilatore di Xamarin consente di compilare nel linguaggio intermedio (IL), che viene quindi compilato Just-in-Time (JIT) in assembly nativo all'avvio dell'applicazione.
In iOS invece, il compilatore Ahead Of Time (AOT) di Xamarin compila le applicazioni Xamarin.iOS direttamente nel codice assembly ARM nativo.
In entrambi i casi, il risultato è indistinguibile da quello prodotto con gli ambienti di sviluppo predefiniti delle due piattaforme e quindi i pacchetti APK e IPA potranno essere distribuiti negli store e nei dispositivi esattamente allo stesso modo.

Xamarin Forms

Xamarin.Forms invece, lavora al di sopra di Xamarin.Android e Xamarin.iOS e permette di realizzare interfacce utente multipiattaforma in C# e XAML (Extensible Application Markup Language).
Tramite questo approccio, viene offerto allo sviluppatore un sottoinsieme delle API comune alle diverse piattaforme richiesto per scrivere la gran parte di un'app in una codebase unificata.

Materiale

Setup iniziale

Si comincia col dire che l'approccio presentato in questa pagina è "Xamarin Native".
Supponendo di usare Visual Studio 2019 e che sia già installato, occorre che si sia aggiunto anche il pacchetto "Mobile development with .NET" con Xamarin:

MS VS Android inst 01.png
MS VS Android inst 02.png

Per eseguire il simulatore occorre abilitare le seguenti funzionalità di Windows:

MS VS Android inst 03.png

ed anche:

MS VS Android inst 04.png

Applicazioni

Creazione nuovo progetto

MS VS Android new Sln 01.png
MS VS Android new Sln 02.png

Particolare della User Iterface principale e del punto di ingresso della applicazione:

CSharp Android Parti principali della App.png

Codice

Messaggi di dialogo e popup

Per visualizzare un semplice messaggio che scompare dopo il tempo predefinito nella variabile ToastLength.Long:

Toast.MakeText(ApplicationContext, "Ciao ciao", ToastLength.Long).Show();

Oppure, se inserito in un handler dove il sender è istanziato con la vista corrente:

View view = (View)sender;
Snackbar.Make(view, "Messaggio cucu", Snackbar.LengthShort).Show();

Anche in questo caso il messaggio è a tempo, Snackbar.LengthShort, ma ci sono più opzioni ad esempio per non farlo sparire basta: Snackbar.LengthIndefinite

Per visualizzare il classico messaggio di dialogo:

using AlertDialog = Android.App.AlertDialog;
//...
AlertDialog.Builder dialog = new AlertDialog.Builder(this);
AlertDialog alert = dialog.Create();
alert.SetIcon(Android.Resource.Drawable.IcDialogInfo);
alert.SetTitle("Alert");
alert.SetMessage("Ciao questo è un messaggio che richiede attenzione");
alert.SetButton("OK", (c, ev) => {
                                  });
alert.Show();

Accesso ai files

Android raggruppa il file System in due tipi diversi di archiviazione:

  • Archiviazione interna – si tratta di una parte del file system a cui è possibile accedere solo dall'applicazione o dal sistema operativo.
  • Archiviazione esterna – si tratta di una partizione per l'archiviazione di file accessibile da tutte le app, dall'utente e possibilmente da altri dispositivi. In alcuni dispositivi, l'archiviazione esterna può essere rimovibile (ad esempio una scheda SD).

Archiviazione Interna

Archiviazione interna si riferisce alla memoria che Android alloca al sistema operativo, apk e per le singole app.
Questo spazio non è accessibile ad eccezione del sistema operativo o delle app.
Android alloca una directory nella partizione di archiviazione interna per ogni app.
In Android 6,0 o successiva, il backup dei file nell'archiviazione interna può essere eseguito automaticamente da Google usando la funzionalità di backup automatico developer.android.com.
L'archiviazione interna presenta gli svantaggi seguenti:

  • I file non possono essere condivisi.
  • I file verranno eliminati quando l'app viene disinstallata.
  • Lo spazio disponibile nell'archiviazione interna potrebbe essere limitato.


Per l'archiviazione interna si usano degli helper della libreria System Novell.Essentials, DOC

// ARCHIVIAZIONE INTERNA
// ES.:    /data/user/0/com.companyname.phoneword/files/ProvaTesto.txt
string storePath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal);
string testFileName = "ProvaTesto.txt";
TxtV_HelpDebug.Text = Path.Combine(storePath, testFileName);

Archiviazione Esterna

Eaiatono due tipo diversi di file per l'archiviazione esterna:

  • file privati, sono file specifici dell'applicazione. Android li archivia in una directory specifica. Tali file sono ancora visibili e accessibili da altre app nel dispositivo, non sono sotto la protezione speciale di Android.
  • file pubblici, sono file che non specifici dell'applicazione e che devono essere condivisi liberamente.
//Esempio di radice percorso ARCHIVIAZIONE ESTERNA:
//                  //storage/emulated/0/
//
//Segue codice accesso archiviazione esterna mediante codice
string tmpArchivePath = string.Empty;
Java.IO.File extStorage = Android.OS.Environment.ExternalStorageDirectory;
Java.IO.File dir = new Java.IO.File(extStorage.AbsolutePath);
tmpArchivePath = dir.AbsolutePath;  //storage/emulated/0/
Permessi \ Autorizzazioni

Android considera l'accesso a una risorsa di archiviazione esterna come una attività pericolosa per cui occorre preventivamente un'autorizzazione esplicita di accesso. Occorre modificare opportunamente il file AndroidManifest.xml. Per maggiori dettagli sui permessi leggere docs.microsoft.com
Oppure procedere come segue ceccando il flag come segue dopo aver premuto il tasto dx del mouse sul progetto dell'applicazione:

Android cfg write external file 01.png

Quindi aggiungere anche la possibilità di leggerli, un AndroidManifest.xml tipo potrebbe essere:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:versionCode="1" android:versionName="1.0" package="it.aino.bankpassword" android:installLocation="auto">
	...
	<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
	<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
</manifest>

Oltre a quanto elencato occorre anche che l'utente dia un consenso esplicito al primo avvio dell'applicazione questo è necessario dalla build version del SDK API >= 23 (ottenibile con il codice: int sdkApi_info = (int)Build.VERSION.SdkInt;). Esiste del codice riusabile per effettuare questa verifica e richiedere l'intervento dell'utente:

using Android.Support.V4.Content;
using Android.Content.PM;
using Android;
using Android.Support.V4.App; 
//...
bool isAccessDenied = false;
if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.ReadExternalStorage) != (int)Permission.Granted)        
{
	TxtV_HelpDebug.Text += "\r\nNON Si dispone dei permessi di LETTURA per l'archivio esterno";
	isAccessDenied = true;
}
if (ContextCompat.CheckSelfPermission(this, Manifest.Permission.WriteExternalStorage) != (int)Permission.Granted)
{
	TxtV_HelpDebug.Text += "\r\nNON Si dispone dei permessi di SCRITTURA per l'archivio esterno";
	isAccessDenied = true;
}
if (isAccessDenied)
{
	var permissions = new string[] 
						{ 
							Manifest.Permission.ReadExternalStorage, 
							Manifest.Permission.WriteExternalStorage 
						};
	ActivityCompat.RequestPermissions(this, permissions, 9); // RequestCode REQUEST_FOLDER_PERMISSION = 9?
}

Il codice di prima, dopo l'intervento esplicito dell'utente, genera l'evento OnRequestPermissionsResult il cui override si presume nella MainActivity.


// ARCHIVIAZIONE ESTERNA
// ES.:    /
Java.IO.File sdCard = Android.OS.Environment.ExternalStorageDirectory;
Java.IO.File dir = new Java.IO.File(sdCard.AbsolutePath);
string storePath = dir.AbsolutePath; //   storage/emulated/o/

Handlers Eventi

Esempio di codice associato al primo evento all'avvio della applicazione, OnCreate. Si intercettano gli oggetti della UI e si associa del codice come Handler.

namespace PhoneWord
{
    [Activity(Label = "@string/app_name", Theme = "@style/AppTheme.NoActionBar", MainLauncher = true)]
    public class MainActivity : AppCompatActivity
    {
        static readonly List<string> g_phoneNumbers = new List<string>();
 
        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
            Xamarin.Essentials.Platform.Init(this, savedInstanceState);
            SetContentView(Resource.Layout.activity_main);
 
            Android.Support.V7.Widget.Toolbar toolbar = FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.toolbar);
            SetSupportActionBar(toolbar);
 
            FloatingActionButton fab = FindViewById<FloatingActionButton>(Resource.Id.fab);
            fab.Click += FabOnClick;
 
            // --- Il mio codice qui
            // Get our UI controls from the loaded layout
            EditText phoneNumberText = FindViewById<EditText>(Resource.Id.PhoneNumberText);
            TextView translatedPhoneWord = FindViewById<TextView>(Resource.Id.TranslatedPhoneword);
            Button translateButton = FindViewById<Button>(Resource.Id.TranslateButton);
            Button translationHistoryButton = FindViewById<Button>(Resource.Id.TranslationHistoryButton);
 
            // Add code to translate number
            translateButton.Click += (sender, e) =>
            {
                // Translate user's alphanumeric phone number to numeric
                string translatedNumber = Core.PhonewordTranslator.ToNumber(phoneNumberText.Text);
                if (string.IsNullOrWhiteSpace(translatedNumber))
                {
                    translatedPhoneWord.Text = string.Empty;
                }
                else
                {
                    translatedPhoneWord.Text = translatedNumber;
                    g_phoneNumbers.Add(translatedNumber);
                    translationHistoryButton.Enabled = true;
                }
            };
 
            translationHistoryButton.Click += (sender, e) =>
            {
                var intent = new Intent(this, typeof(TranslationHistoryActivity));
                intent.PutStringArrayListExtra("phone_numbers", g_phoneNumbers);
                StartActivity(intent);
            };
        }
//....
}

Altra via diversa dalla precedente per associare un handler ad un evento legato ad un oggetto della UI.

//...
protected override void OnCreate(Bundle savedInstanceState)
{
     base.OnCreate(savedInstanceState);
     Xamarin.Essentials.Platform.Init(this, savedInstanceState);
     SetContentView(Resource.Layout.activity_main);
//...
     FloatingActionButton BtnMioBottoneAzione = FindViewById<FloatingActionButton>(Resource.Id.fab);
     BtnMioBottoneAzione.Click += FabOnClick;
}
 
private void FabOnClick(object sender, EventArgs eventArgs)
{
     View view = (View) sender;
     Snackbar.Make(view, "Replace with your own action", Snackbar.LengthLong)
          .SetAction("Action", (Android.Views.View.IOnClickListener)null).Show();
}

Multi activities

Per creare una ulteriore Activity (da aggiungere alla MainActivity), tasto dx sul progetto e cliccare su "Add", poi "New Item", quindi "Activity" (aggiungerà una classe: "MiaActivity.cs").
Ora per lanciare l'Activity basta andare all'handler del evento dell'interfaccia che si vuole faccia aprire l'activity ed aggiungere il seguente codice:

 StartActivity(typeof(MiaActivity));

Se invece si voglio passare dei dati occorre creare una Intent a cui si aggiungono i dati nella forma (CHIAVE, VALORE):

Intent intent = new Intent(this, typeof(MiaActivity));
intent.PutExtra("Campo1", "Valore, Il mio nome");
intent.PutExtra("Campo2", 1001);
StartActivity(intent);

ovvero all'oggetto Intent si aggiungono i dati con il metodo .PutExtra("CHIAVE", VALORE);. Così si posson passare solo dati di tipo primitivo. Doc:

Nella Activity "MiaActivity" i dati passati si recuperano così:

string nome= Intent.GetStringExtra("Campo1");
int valore = Intent.GetIntegerExtra("Campo2");

Per ricevere indietro dati da una Activity aperta (MiaActivity), si usera invece l'istruzione startActivityForResult() quindi per ottenerne i valori si usa una CallBack creando il metodo onActivityResult().

Navigare tra più pagine o viste

Xamarin.Forms supporta la navigazione tra oggetti incastonati (navigazione gerarchica):

  • NavigationPage, dove la prossima pagina scivola dentro,
  • TabbedPage, ?
  • CarouselPage, che consente di spostarsi avanti ed indietro tra pagine.

On top of this, all pages also supports PushModalAsync()</code> which just push a new page on top of the existing one.

Se hai bisogno che l'utente non possa tornare indietro alla pagina precedente (usando gesture o il bottone hardware per tornare indietro), puoi mantenere la stessa pagina visualizzata e sostituirla semplicemente nel suo Content.

The suggested options of replacing the root page works as well, but you'll have to handle that differently for each platform.


Lista ListView

Il seguente è in una griglia (GridLayout) 9x3

 
<ListView
        android:minWidth="25px"
        android:minHeight="25px"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/LVMatchItems"
        android:layout_row="6"
        android:layout_column="0"
        android:layout_columnSpan="3" />

Lato codice:

//..
LVMatchItems = FindViewById<ListView>(Resource.Id.LVMatchItems);
// Associo un handler:
LVMatchItems.ItemClick += LVMatchItems_ItemClick;
//Metodo di popolamento con un array stringa
LVMatchItems.Adapter = new ArrayAdapter(this, 
                                        Android.Resource.Layout.SimpleListItem1,
                                        m_arrStrFilteredItem ?? new string[0]);
 
//Metodo Handler:
private void LVMatchItems_ItemClick(object sender, AdapterView.ItemClickEventArgs e)
{
	var t = m_arrStrFilteredItem[e.Position];
	Toast.MakeText(this, t, ToastLength.Long).Show();
}

Librerie

Xamarin.Android.Support.Design

Collaudo

E' possibile creare un emulatore di dispositivo ("Android Emulator", immagine del cellulare che segue), questo sarà per default il depositario dell'applicazione in sviluppo (ciò dopo opportune impostazioni e configurazioni).
Per analizzare le risorse dell'emulatore (ad es. il suo FileSystem) è possibile usare l'applicativo "Android Device Emulator". Nella seguente immagine è possibile prendere visione di una configurazione tipo.

Android Device Monitor and Emulator 01.png

Risorse

Risuluzione di Problemi

Deploy fallisce

application deploy on Android 11 fails "ADB1000: deployment failed operation not permitted"

  • Tasto dx sul progetto
  • Tab "Android Options"
  • togliere il check su "Use Fast Deployment (debug mode only)"
Android error ADB1000.png

Mappa e Link


C# | Android App | XAMARIN Tips | Appunti


Visual Studio | Windows Form | MS SQL | Dizionario


Parole chiave:

Author