Swift 3 e la Configurazione Remota

Oggi parleremo di Configurazione Remota o anche Remote Configuration, ma cosa è?

Prendiamo come esempio l’applicazione Costituzione Italiana. Questo genere di progetto ha al suo interno alcune tecnologie basilari come Google Analytics, Arena Daemon e altri servizi esterni.
Cosa accade se ad un certo punto volessi disattivarne uno? Esatto, dovrei fare una nuova build e inviarla ad Apple.

Se volessi fare dei test A/B sarebbe un vero dramma.

La soluzione è avere una configurazione remota. L’app quindi prima di avviare i servizi controlla un file di configurazione remoto ed applica i comportamenti indicati.

Sul mio server ho inserito un piccolo file JSON:

{
	"enableGoogleAds": true,
	"enableGoogleAnalytics": true
}

L’app quindi si comporterà in base a questi due valori.

A questo punto non ci rimane che scrivere il codice Swift e per l’occasione ne approfitteremo per iniziare a scrivere qualcosa nella sua ultima versione, la 3.

Creiamo quindi una classe chiamata RemoteConfiguration.
Dovrà utilizzare il pattern Singleton così da avere una singola istanza per tutta la vita della nostra app.

class RemoteConfiguration {
	static let sharedInstance = RemoteConfiguration()
}

Aggiungiamo poi una proprietà chiamata baseURL:URL che andremo a configurare in un secondo momento. Poi ci serve un metodo che ci permette di recuperare il file. Questo è il momento giusto per utilizzare una callback, quindi creiamo un metodo chiamato configuration e come argomento deve accettare una closure di questo tipo: (dictionary:[String:AnyObject]))->().

class RemoteConfiguration {
	static let sharedInstance = RemoteConfiguration()
	var baseURL:URL?

	func configuration(handler:@escaping (_ dictionary:[String:AnyObject]?)->()) {

	}
}

La logica che ho pensato è questa: Se i dati non sono mai stati scaricati vado a recuperarli dall’URL indicato,  se invece li ho già scaricati non farò alcuna chiamata al server ed utilizzerò i dati in memoria.
Per farlo serve una proprietà privata configurationDictionary in cui salveremo i dati.

class RemoteConfiguration {
	static let sharedInstance = RemoteConfiguration()
	var baseURL:URL?

	private var configurationDictionary:[String:AnyObject]?

	func configuration(handler:@escaping (_ dictionary:[String:AnyObject]?)->()) {

	}
}

Adesso andiamo a creare la magia.
Per convenienza ho scritto una piccola classe chiamata Request che si occupa di scaricare i dati dalla rete in modo asyncrono, la troverete nel file Playground.

Request ritorna un oggetto Data che andremo a convertire in un dizionario usando la classe JSONSerialization e il metodo jsonObject.
Questo metodo solleva un’eccezione ma in questo momento non ci interessa controllare l’eventuale errore e quindi  possiamo utilizzare try? in modo da avere un oggetto Optional. In alternativa avremmo dovuto utilizzare un blocco do {} catch {}.

class RemoteConfiguration {
	static let sharedInstance = RemoteConfiguration()
	var baseURL:URL?
	
	private var configurationDictionary:[String:AnyObject]?
	
	func configuration(handler:@escaping (_ dictionary:[String:AnyObject]?)->())
	{
		guard let baseURL = baseURL else { assertionFailure("URL cannot be nil"); return }
		
		if let configuration = configurationDictionary
		{
			handler(configuration)
		}
		else
		{
			Request.url(url: baseURL, completion: {
				data, error in
				
				if let data = data, error == nil
				{
					if let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String:AnyObject]
					{
						handler(json)
						return
					}
				}
				
				handler(nil)
				
			})
		}
	}
}

È arrivato il momento di provare il nostro codice, configuriamo RemoteConfiguration e richiediamo i dati:

let rc = RemoteConfiguration.sharedInstance
rc.baseURL = URL(string: "http://localhost/pills/p-01/remote-config.json")
rc.configuration { configuration in
	if let configuration = configuration
	{
		debugPrint(configuration)
	}
}

Se tutto è andato bene dovremmo vedere nel debugger la stampa del dizionario:

["enableGoogleAds": 1, "enableGoogleAnalytics": 1]

Questa classe è un ottimo punto di partenza nella nostra implementazione di una configurazione remota. Personalmente la trovo davvero molto utile per fare test A/B e per attivare o disattivare funzionalità o servizi senza dover ricompilare tutto.

Ovviamente è un punto di partenza, ci sono molte ottimizzazioni da poter fare e funzionalità da implementare. Potrebbero essere argomento nei prossimi articoli.

Potete scaricare il file Playground da GitHub.

Happy coding!

Implementare Facebook login in un’app iOS e PHP

Qualche mese fa parlavo con dei miei colleghi riguardo alla migliore tecnica per implementare il classico Login Facebook all’interno di un servizio tramite l’app iOS in modo sicuro.

La difficoltà più grande che abbiamo avuto è stata quella di capire quale migliore tecnica ci permettesse di sfruttare l’SSO in modo intelligente. In particolar modo c’erano due punti chiave:

  1. L’utente non deve rendersi conto di niente e ritrovarsi loggato/registrato
  2. Il servizio (quindi il CMS che gestisce l’anagrafica utenti) deve registrare un  utente valido

Poi qualche giorno fa mi sono ritrovato a dover implementare OAuth e SSO su segnala.net e mi sono dovuto rimboccare le maniche per implementare quanto avevo descritto.

La situazione era questa: CMS Apache, PHP, MySQL e applicazione iOS (Swift).
Il CMS gestisce l’anagrafica e quindi se un utente deve accedere, prima deve loggarsi o registrarsi.

Nel CMS ho installato un server OAuth così da poter avere accesso alle API solo tramite autenticazione OAuth, poi ho iniziato a scrivere il codice che si occupa di gestire l’SSO.

La logica è descritta nell’immagine:

Logica SSO-OAuth per implementare il Facebook login

*[SSO Data] sono i dati richiesti per la verifica dell’accesso. Ad esempio con Facebook ho utilizzato il Token e lo User ID.

I passaggi logici sono questi:

  • L’app richiede a Facebook, tramite l’SDK, l’accesso. Facebook quindi mi passa il suo Token e lo user ID.
  • L’app quindi richiede tramite API al mio CMS l’accesso tramite questi due dati.
  • Il CMS accede quindi a Facebook tramite Graph e controlla che il token sia valido e che corrisponda allo user ID passato
  • Se tutto va a buon fine va a pescare l’indirizzo email dell’utente e qualche altra informazione necessaria ad una eventuale registrazione
  • Una volta presi i dati necessari va a controllare se esiste un utente registrato con l’email trovata
  • Se esiste crea un token di accesso e lo passa all’app
  • Se non esiste crea un nuovo utente con i dati Facebook, crea un token di accesso e lo passa all’app

Questa logica è applicabile ad ogni tipo di SSO Provider e mi è sembrata molto sicura sia dalla parte dell’app che da quella del server.

Se avete domande o suggerimenti commentate pure!

Come localizzare la tua applicazione in molte lingue

Uno dei punti che anche Apple consiglia di concentrarsi per avere un’applicazione di successo è quello di renderla localizzata in molte lingue.

Per questo ha anche creato una guida apposita. Ecco quindi una semplice guida su come localizzare la vostra applicazione.

La prima cosa da fare è creare un nuovo file all’interno del progetto. Tramite il pannello di creazione dei file: Menu File -> New File -> Resources -> Strings File. Diamogli il nome “Localizable.strings”.

Questo file serve appositamente per localizzare le stringhe.

Nel progetto quindi, quando dovete inserire una stringa, usate la funzione: NSLocalizedString().
Per rendere le cose semplici ho creato una UILabel chiamata “label”.

self.label.text = NSLocalizedString(@"Questo testo è localizzato", @"Commento su questa stringa");

La funzione accetta due parametri, la stringa da localizzare e un commento alla stringa. Il primo parametro è obbligatorio mentre nel secondo possiamo può passare anche nil. E’ comunque consigliato utilizzare questo parametro per inserire un commento utile a chi poi andrà a localizzare il testo.

Adesso quindi andiamo ad inserire questi dati nel file di stringhe precedentemente creato. Apriamo il file Localizable.strings e scriviamo:

/* Commento su questa stringa */
"Questo testo è localizzato" = "This text is localized in english";

Come vedete, a destra c’è  il testo da localizzare, a sinistra c’è il testo localizzato. E’ importante mantenere la sintassi corretta altrimenti il file non sarà correttamente utilizzato.

Adesso, se lanciamo l’app, vedremo nella nostra label il testo “This text is localized in english”.

Traduciamo quindi le nostre stringhe nella lingua Italiana.
Selezioniamo il file “Localizable.strings” nel progetto e apriamo il pannello dell’inspector. Nel tab “File” c’è una sezione “Localization” e un pulsante “Localize”. Premetelo, il sistema vi chiederà in che lingua base è il file, selezionate English.

Rendi il file localizzato con l'Inspector

Adesso selezionate il progetto dalla lista dei files e selezionate ancora il progetto (non il target).
Nel pannello centrale ci sono varie sezioni, tra cui “Localizations”, da qui potete inserire tutte le lingue che desiderate. Premete quindi + ed aggiungete l’italiano. Dal pannello che compare, selezionate solamente il file “Localizable.strings” perché per adesso vogliamo localizzare solamente questo file.

Pannello Localizations Seleziona solamente il file Localizable.strings

Perfetto, adesso noterete che il file “Localizable.strings” ha una freccetta di lato.

Adesso il file ha la linguetta per aprire le lingue

Se la cliccate si espanderà mostrando le varie lingue. Selezionate quindi il file in lingua desiderata e traducete la frase:

/* Commento su questa stringa */
"Questo testo è localizzato" = "Questo file è tradotto in italiano";

Pronti, la vostra applicazione è tradotta in Italiano e in Inglese.

Curiosità – Localizzare altri files

E’ possibile, con lo stesso metodo, localizzare anche i file Storyboard e Xib. In questo modo potrete avere direttamente i file dell’interfaccia grafica localizzata.

Nel caso in cui la UI sia complessa, consiglio di utilizzare sempre le stringhe ed avere un file XIB/Storyboard unico.