Iniciando com Anko Kotlin. Intenções no Android

Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba grátis conteúdos Android sem precedentes! Você receberá um email de confirmação. Somente depois de confirma-lo é que eu poderei lhe enviar os conteúdos semanais exclusivos. Os artigos em PDF são entregues somente para os inscritos na lista.

Email inválido.
Blog /Android /Iniciando com Anko Kotlin. Intenções no Android

Iniciando com Anko Kotlin. Intenções no Android

Vinícius Thiengo
(8212)
Go-ahead
"O método consciente de tentativa e erro é mais bem-sucedido que o planejamento de um gênio isolado."
Peter Skillman
Prototipagem Android
Capa do curso Prototipagem Profissional de Aplicativos
TítuloAndroid: Prototipagem Profissional de Aplicativos
CategoriasAndroid, Design, Protótipo
AutorVinícius Thiengo
Vídeo aulas186
Tempo15 horas
ExercíciosSim
CertificadoSim
Acessar Curso
Quer aprender a programar para Android? Acesse abaixo o curso gratuito no Blog.
Lendo
TítuloManual de DevOps: como obter agilidade, confiabilidade e segurança em organizações tecnológicas
CategoriaEngenharia de Software
Autor(es)Gene Kim, Jez Humble, John Willis, Patrick Debois
EditoraAlta Books
Edição
Ano2018
Páginas464
Conteúdo Exclusivo
Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba gratuitamente conteúdos Android sem precedentes!
Email inválido

Tudo bem?

Neste artigo vamos iniciar os estudos da library Anko, biblioteca criada pela JetBrains para trabalhar como um helper às várias APIs presentes no Android SDK.

Com a library Anko, APIs como: SQLite, Intent, DialogToast e até mesmo a construção de layouts dinâmicos. O trabalho com todas estas, e ainda mais, é facilitado.

Aplicativo Android com a biblioteca Anko

Aqui vamos utilizar um projeto de aplicativo institucional, comum para empresas que não têm funcionalidades a oferecer em termos de aplicativos, mas mesmo assim querem ter mais um canal de contato com o cliente.

Neste projeto vamos utilizar algumas das facilidades do trabalho com a biblioteca Anko, mais precisamente, com a API de intenções desta library.

No decorrer do estudo também vamos abordar outros assuntos, como: Log, Lambdas e argumentos nomeados.

Antes de prosseguir, saiba que se este é seu primeiro contato com o Kotlin, é importante que você estude o conteúdo do artigo Kotlin Android, Entendendo e Primeiro Projeto e depois prossiga com este.

Nele também há vídeo caso prefira este formato. Digo isso, pois os pontos de configuração e inicialização do Kotlin não serão abordados aqui.

Antes de prosseguir, não esqueça de se inscrever 📫na lista de e-mails do Blog para receber em primeira mão todos os conteúdos de desenvolvimento Android exclusivos aqui do Blog.

A seguir os tópicos que estaremos abordando:

Anko

Anko é uma biblioteca Kotlin criada pela JetBrains para facilitar ainda mais o desenvolvimento de aplicativos Android com esta linguagem. Dentro da library há linhas de APIs que são atendidas. São elas:

  • Anko Commons: incluí helpers para APIs do Android SDK. Um desses é para uso de Intent, o conteúdo principal neste artigo;
  • Anko Layouts: permite a construção de layouts dinâmicos sem uso de XMLs;
  • Anko SQLite: contém uma interface que facilita ainda mais o trabalho com essa base de dados local do Android;
  • Anko Coroutines: API baseada na kotlinx.coroutines que permite o trabalho com pool de Thread e referências fracas quando necessárias, por exemplo.

Mesmo a linguagem em si sendo simples, digo, o Kotlin, é possível que nós tenhamos códigos tão grandes quanto quando construindo aplicativos Android em Java, isso, pois algoritmos como, por exemplo, o de solicitação de aplicativo de email têm uma série de linhas de código necessárias para que a funcionalidade seja atendida.

Veja um exemplo utilizando somente a linguagem Kotlin, sem a biblioteca Anko:

...
val intent = Intent(Intent.ACTION_SENDTO)
intent.data = Uri.parse("mailto:")
intent.putExtra(Intent.EXTRA_EMAIL, arrayOf(email))
intent.putExtra(Intent.EXTRA_SUBJECT, assunto)
intent.putExtra(Intent.EXTRA_TEXT, corpo)
startActivity(intent)
...

 

Com o apoio da library Anko teríamos ainda o mesmo código definido anteriormente, porém encapsulado na API da library que responde as funcionalidades de intenções. Segue código com a Anko library em uso:

...
email(email, assunto, corpo)
...

 

Na função acima todos os argumentos são do tipo String. O import utilizado foi o seguinte:

...
import org.jetbrains.anko.*
...

 

Antes de prosseguir, saiba que o Anko é uma library grande como qualquer uma outra que faça não ser viável explica-la em apenas um artigo e vídeo.

Devido a isso, aqui vamos trabalhar principalmente com a API de intenções, presente na parte Commons. Assim o entendimento será facilitado. Posteriormente estaremos abordando outras APIs desta library.

Configuração inicial para trabalhar com a Anko Commons

Caso você siga as instruções apresentadas na página oficial da library Anko, perceberá que seu código não funcionará, digo, a sincronização do projeto, pois não foi definido em lugar algum a variável $anko_version.

Até o momento da construção deste artigo essa variável estava presente nos códigos de configuração inicial, do Anko Commons, apresentados na página oficial.

Eu particularmente prefiro definir diretamente a versão mais estável da library, mas, acredite, futuramente vamos passar a utilizar o trabalho com variável, pois haverá outras referências a APIs Anko.

Para trabalhar com o Anko Commons, devemos ter a seguinte referência no Gradle App Level:

...
dependencies {
compile 'org.jetbrains.anko:anko-commons:0.10.1'
}
...

 

Para saber a versão mais atual e estável da library, entre no bintray dela em: https://bintray.com/jetbrains/anko/anko.

É possível, como acontece com o Google Play Services, utilizar uma referência "não otimizada" da Anko library onde os principais artefatos / APIs serão incluídos ao projeto, incluindo alguns que você nunca utilizará e estarão ali somente para aumentar o tamanho, em bytes, do aplicativo:

...
dependencies {
compile 'org.jetbrains.anko:anko:0.10.1'
}
...

 

Ressaltando que aqui estamos somente trabalhando a referência as APIs Commons de Anko, pois há muitas outras que virão em artigos e vídeos posteriores.

O que conseguimos com o Anko Commons?

Com Commons temos quatro linhas de APIs sendo atendidas:

  • Intents;
  • Dialogs, progress e toasts;
  • Logging (LogCat);
  • Recursos e dimensões.

Com o Commons vamos poder utilizar funcionalidades comuns no desenvolvimento de apps Android, realizando simples invocações.

A seguir um exemplo para cada uma das linhas de APIs atendidas pela Anko Commons.

Exemplo "Intents" para invocar uma página Web no navegador do device:

...
browse("https://www.thiengo.com.br")
...

 

O equivalente, do código acima, em nosso algoritmo Kotlin sem o Anko Commons, seria:

...
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse("https://www.thiengo.com.br")
startActivity(intent)
...

 

Exemplo na linha "Dialogs, progress e toasts" para apresentação de uma mensagem Toast em um período equivalente a Toast.LENGTH_LONG:

...
longToast("Seja um desenvolvedor Android")
...

 

O equivalente, do código acima, em nosso algoritmo Kotlin sem o Anko Commons, seria:

...
Toast
.makeText(this, "Seja um desenvolvedor Android", Toast.LENGTH_LONG)
.show()
...

 

Agora um exemplo na linha "Logging" para a apresentação de uma info no LogCat, ou logs do Android Studio:

...
class MainActivity : AppCompatActivity(), AnkoLogger {
override fun onCreate(savedInstanceState: Bundle?) {
...
info("Apenas uma informação. Equivalente a Log.i()")
}
}
...

 

A chave que aparecerá nos logs será o nome da atividade onde algum dos métodos de nível (verbose, debug, info, warn, error, wtf) é invocado.

O código anterior, a princípio, não tem ganho quando comparando a versão sem a Anko library, digo, ao menos em quantidade de código, pois em leitura tem:

...
Log.i("MainActivity", "Apenas uma informação.");
...

 

Por fim o exemplo de "Recursos e dimensões". Caso você seja um programador Android que já tenha de utilizado inúmeras vezes a conversão de densidade de pixel (DIP) para pixels devido a alguns métodos como LayoutParams.margins(), você vai gostar da função a seguir:

...
/*
* CONVERTENDO 16 DIP PARA PIXELS, QUE EM UMA SCREEN
* DIFERENTE DE MDPI TERÁ UM VALOR DIFERENTE DE 16 PIXELS
* */
dip(16);
...

 

Há alguns outros métodos de conversão como: sp() e px2dip().

Abertura de Activity e Intent Callers padrões

Para somente abrir uma Activity, utilizando a Anko Commons teríamos o seguinte código:

...
startActivity<SegundaActivity>()
...

 

Para definir algumas flags e até mesmo alguns dados a serem trafegados para a outra atividade, uma possibilidade seria: 

...
startActivity(
intentFor<SegundaActivity>(
"nome" to "Thiengo",
"site" to "https://www.thiengo.com.br")
.singleTop() )
...

 

O código acima, em Kotlin e sem o Anko Commons, é equivalente ao seguinte:

...
val intent = Intent(this, SegundaActivity::class.java)
intent.putExtra("nome", "Thiengo")
intent.putExtra("site", "https://www.thiengo.com.br")
intent.setFlag(Intent.FLAG_ACTIVITY_SINGLE_TOP)
startActivity(intent)
...

 

Sobre os dois argumentos, nome e site, você na verdade pode definir quantos quiser, pois é um parâmetro do tipo Pair que está sendo utilizado.

Alguns callers que têm interfaces próprias no Anko, pois são amplamente utilizados no desenvolvimento de aplicativos, são os seguintes:

  • Email: email("algum_email@gmail.com", ["assunto"], ["texto corpo"]);
  • Navegador: browse("https://www.thiengo.com.br");
  • Chamada telefônica (esse necessita da permissão android.permission.CALL_PHONE que é uma dangerous permission): makeCall("999887766");
  • Chamada de aplicativo de envio de SMS: sendSMS("999887766", ["texto corpo"]);
  • Compartilhamento de conteúdo em texto: share("texto corpo", ["assunto"]).

Tudo que está entre colchetes, [], é opcional.

Estaremos utilizando também todos estes callers e códigos wrapper de Intent no projeto de exemplo, vamos a ele.

Projeto Android de exemplo

Nosso projeto de exemplo é simples, é a versão institucional do Blog Thiengo [Calopsita].

Esse tipo de aplicativo é muito comum para empresas que não têm muito a oferecer via apps mobile a não ser um meio de contato com vendedores ou o suporte da própria empresa.

Um exemplo de empresa deste tipo? Uma de corte de granitos é um bom exemplo.

Antes de prosseguir vou assumir que você ou já está com o Android Studio 3+ ou está com uma versão inferior, porém, neste último caso, com o plugin do Kotlin já configurado, como fizemos no artigo: Kotlin Android, Entendendo e Primeiro Projeto.

Para ter acesso ao projeto por completo, incluindo os arquivos de imagens e configurações de IDE, entre no GitHub a seguir: https://github.com/viniciusthiengo/thiengo-institucional-app-kotlin.

Com o Android Studio aberto, crie um novo projeto iniciando com uma Navigation Drawer Activity. Coloque o nome "Thiengo Institucional App".

Caso você esteja com o Android Studio 3+, já inicie com o app sendo um Kotlin.

Ao final desta primeira configuração, sem a library Anko Commons, teremos o seguinte aplicativo (ainda sem funcionalidade alguma):

Aplicativo Android sem a biblioteca Anko

Configurações Gradle

A seguir o código inicial do Gradle Project Level, ou build.gradle (Project: ThiengoInstitucionalApp):

buildscript {
ext.kotlin_version = '1.1.2-4'
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

allprojects {
repositories {
jcenter()
}
}

task clean(type: Delete) {
delete rootProject.buildDir
}

 

O que foi adicionado, devido ao uso do Kotlin, está em destaque.

Assim o Gradle App Level, build.gradle (Module: app), ainda sem o Anko Commons e sem uma library de solicitação de permissão em tempo de execução que estaremos utilizando para poder realizar ligações telefônicas:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "br.com.thiengo.thiengoinstitucionalapp"
minSdkVersion 14
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support:design:25.3.1'
testCompile 'junit:junit:4.12'

compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
}
repositories {
mavenCentral()
}

 

Códigos referentes ao Kotlin foram destacados.

Se na época que você estiver estudando este artigo haja versões mais atuais do Gradle, utilize elas, pois tende a não ter interferência no conteúdo apresentado.

Configurações AndroidManifest

Para a configuração do AndroidManifest.xml teremos, inicialmente, duas atividades:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="br.com.thiengo.thiengoinstitucionalapp">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">

<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity
android:name=".SMSActivity"
android:label="@string/sms_activity_label"
android:theme="@style/AppTheme.NoActionBar" />
</application>
</manifest>

 

Posteriormente, já trabalhando com o Anko, voltaremos a este arquivo.

Configurações de estilo

Para os arquivos XML de estilo não temos muitas modificações, o de String fica com a maior parte delas.

Vamos iniciar com as marcações de cores, /res/values/colors.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#FF9800</color>
<color name="colorPrimaryDark">#F57C00</color>
<color name="colorAccent">#FF5722</color>
</resources>

 

Assim o arquivo XML de String, /res/values/strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Thiengo Institucional App</string>
<string name="sms_activity_label">Contato SMS</string>

<string name="navigation_drawer_open">Abrir navigation drawer</string>
<string name="navigation_drawer_close">Fechar navigation drawer</string>

<string name="explicacao_permissao">
Para que seja possível realizar a ligação telefônica partindo do aplicativo,
libere a permissão de ligação.
</string>

<string name="texto_institucional">
Lorem Ipsum é simplesmente uma simulação de texto da indústria tipográfica e
de impressos, e vem sendo utilizado desde o século XVI, quando um impressor
desconhecido pegou uma bandeja de tipos e os embaralhou para fazer um livro
de modelos de tipos.\n\n

Lorem Ipsum sobreviveu não só a cinco séculos, como
também ao salto para a editoração eletrônica, permanecendo essencialmente
inalterado. Se popularizou na década de 60, quando a Letraset lançou decalques
contendo passagens de Lorem Ipsum, e mais recentemente quando passou a ser
integrado a softwares de editoração eletrônica como Aldus PageMaker.
</string>

<string name="hint_et_mensagem">Mensagem de texto</string>

<string name="explain_sms_activity">
Na mensagem de texto informe: o assunto principal, seu melhor email e um número
para contato. Pois assim entrarei em contato contigo.
</string>

<string name="enviar_button">Enviar</string>
</resources>

 

Por fim o arquivo de definição de estilo, /res/values/styles.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="AppTheme" parent="Theme.AppCompat.Light">
<item name="android:windowBackground">@drawable/background</item>

<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>

<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>

<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />

<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources>

Atividade de invocação de aplicativo de SMS

Essa atividade contém um formulário de texto de SMS, porém ainda sem funcionalidade, pois essa será adicionada depois da adição do Anko ao projeto.

Vamos iniciar com a apresentação dos XMLs de layout de SMSActivity. A seguir o código de /res/layout/content_sms.xml:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="br.com.thiengo.thiengoinstitucionalapp.SMSActivity"
tools:showIn="@layout/activity_sms">

<TextView
android:id="@+id/tv_explain"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:layout_marginBottom="10dp"
android:text="@string/explain_sms_activity" />

<EditText
android:id="@+id/et_mensagem"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_explain"
android:hint="@string/hint_et_mensagem"
android:inputType="textMultiLine"
android:lines="3" />

<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:text="@string/enviar_button" />
</RelativeLayout>

 

Abaixo o diagrama do layout anterior: 

Diagrama do layout content_sms.xml

Assim o XML de /res/layout/activity_sms.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="br.com.thiengo.thiengoinstitucionalapp.SMSActivity">

<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>

<include layout="@layout/content_sms" />

</android.support.design.widget.CoordinatorLayout>

 

Então o diagrama do layout anterior:

Diagrama do layout activity_sms.xml

Com isso podemos prosseguir com o código Kotlin de SMSActivity:

class SMSActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sms)
val toolbar = findViewById(R.id.toolbar) as Toolbar
setSupportActionBar(toolbar)

getSupportActionBar()?.setDisplayHomeAsUpEnabled(true)
getSupportActionBar()?.setDisplayShowHomeEnabled(true)
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
val id = item.getItemId()
if (id == android.R.id.home) {
finish()
return true
}
return super.onOptionsItemSelected(item)
}
}

 

Provavelmente você deve estar se perguntando: O que significa a interrogação em getSupportActionBar()?.setDisplayHomeAsUpEnabled(true) e em getSupportActionBar()?.setDisplayShowHomeEnabled(true)?

Primeiro saiba que muitas APIs que estaremos utilizando com frequência no Kotlin vieram do Java, logo, a interface delas deve ser respeitada. O que digo com "interface... deve ser respeitada" é que um null pode ser retornado, pois Java não é null-safe.

Obviamente que podemos criar uma classe wrapper, mas acabaríamos aumentando ainda mais a quantidade de código, podendo até mesmo perder vantagem em relação a um código Java.

O operador ?. permite a continuação da cadeia de invocações / retornos caso haja um objeto ou simplesmente retorna um null caso não haja um na invocação que precede o ?..

Simples e útil, pois mesmo sabendo da possibilidade de um valor null, não precisamos de um condicional, if(), para essa verificação.

Atividade principal

Nossa atividade principal é tão simples como a anterior, porém temos um pouco mais de código, pois criamos uma "Navigation Drawer Activity".

Lembrando que depois da criação do projeto, no Android Studio abaixo da versão 3, é preciso abrir o código Java da atividade, ir em "Code", no menu do IDE, e então clicar em "Convert Java File to Kotlin".

Assim podemos prosseguir com os códigos estáticos de nossa atividade principal. A seguir o código XML de /res/layout/content_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="br.com.thiengo.thiengoinstitucionalapp.MainActivity"
tools:showIn="@layout/app_bar_main">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">

<ImageView
android:layout_width="match_parent"
android:layout_height="260dp"
android:layout_marginBottom="16dp"
android:scaleType="centerCrop"
android:src="@drawable/banner_top" />

<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/texto_institucional" />

</LinearLayout>
</ScrollView>

 

Abaixo o diagrama do layout content_main.xml

Diagrama do layout content_main.xml

Agora o XML de /res/layout/app_bar_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="br.com.thiengo.thiengoinstitucionalapp.MainActivity">

<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">

<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />

</android.support.design.widget.AppBarLayout>

<include layout="@layout/content_main" />

</android.support.design.widget.CoordinatorLayout>

 

A seguir o diagrama do XML anterior:

Diagrama do layout app_bar_main.xml

Assim o XML do menu do drawer, /res/menu/activity_main_drawer.xml:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group android:checkableBehavior="single">
<item
android:id="@+id/nav_inicio"
android:checked="true"
android:icon="@drawable/ic_menu_inicio"
android:title="Início" />
<item
android:id="@+id/nav_contato_urgente"
android:icon="@drawable/ic_menu_telefone"
android:title="Contato urgente" />
<item
android:id="@+id/nav_sms"
android:icon="@drawable/ic_menu_sms"
android:title="Envie um SMS" />
<item
android:id="@+id/nav_email"
android:icon="@drawable/ic_menu_email"
android:title="Envie um e-mail" />
<item
android:id="@+id/nav_compartilhar"
android:icon="@drawable/ic_menu_compartilhar"
android:title="Compartilhar ideia" />
<item
android:id="@+id/nav_site"
android:icon="@drawable/ic_menu_site"
android:title="Visitar site" />
</group>
</menu>

 

Segue diagrama do menu anterior:

Diagrama do menu activity_main_drawer.xml

E por fim o XML de /res/layout/activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
android:fitsSystemWindows="true"
tools:openDrawer="start">

<include
layout="@layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent" />

<android.support.design.widget.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="@android:color/white"
android:fitsSystemWindows="true"
app:menu="@menu/activity_main_drawer" />

</android.support.v4.widget.DrawerLayout>

 

Segue diagrama de activity_main.xml:

Diagrama do layout activity_main.xml

Agora o código Kotlin de MainActivity:

class MainActivity :
AppCompatActivity(),
NavigationView.OnNavigationItemSelectedListener {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val toolbar = findViewById(R.id.toolbar) as Toolbar
setSupportActionBar(toolbar)

val drawer = findViewById(R.id.drawer_layout) as DrawerLayout
val toggle = ActionBarDrawerToggle(
this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close)
drawer.addDrawerListener(toggle)
toggle.syncState()

val navigationView = findViewById(R.id.nav_view) as NavigationView
navigationView.setNavigationItemSelectedListener(this)
}

override fun onBackPressed() {
val drawer = findViewById(R.id.drawer_layout) as DrawerLayout
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START)
}
else {
super.onBackPressed()
}
}

override fun onNavigationItemSelected(item: MenuItem): Boolean {
val id = item.itemId

val drawer = findViewById(R.id.drawer_layout) as DrawerLayout
drawer.closeDrawer(GravityCompat.START)
return true
}
}

 

Do código gerado da conversão Java <=> Kotlin, somente removi uma API, a referente ao FloatingActionButton, pois não a utilizaremos aqui.

Assim podemos partir para adição de funcionalidades em nosso aplicativo institucional.

Atualizando o aplicativo para uso do Anko Commons

Antes de prosseguirmos com as atualizações, vamos a um checklist do que teremos de adicionar ao projeto de aplicativo institucional para que ele funcione como esperado:

  • Intenção de chamada telefônica;
  • Intenção de envio de SMS. Esse deve ocorrer em uma atividade separada, a SMSActivity;
  • Intenção de envio de email;
  • Intenção de compartilhamento do site institucional;
  • Intenção de invocação de Web site no navegador do device.

Com isso o usuário conseguirá facilmente entrar em contato com a empresa partindo do app institucional dela. Ele, o cliente, poderá escolher a melhor forma de se comunicar.

Atualizando o Gradle App Level

No Gradle App Level, ou build.gradle (Module: app), adicione a seguinte referência em destaque:

...
dependencies {
...
compile 'org.jetbrains.anko:anko-commons:0.10.1'
}
...

 

Sincronize o projeto.

Caso na época que você esteja estudando este artigo já tenha uma versão mais atual do que a versão 0.10.1 do Anko, utilize essa mais atual.

Interface para envio de email

Vamos primeiro colocar as funcionalidades que têm simples interfaces de requisição, que envolvem somente novos códigos na MainActivity e também não necessitam de permissões específicas.

Na atividade principal, mais precisamente no método onNavigationItemSelected(), adicione o seguinte código em destaque:

...
override fun onNavigationItemSelected(item: MenuItem): Boolean {
val id = item.itemId

if (id == R.id.nav_email) {
email("seu_email@gmail.com",
"Tenho um trabalho Android",
"Olá\n\n")
}

val drawer = findViewById(R.id.drawer_layout) as DrawerLayout
drawer.closeDrawer(GravityCompat.START)
return true
}
...

 

Caso você precise enviar o email de contato para mais de um endereço, simplesmente coloque-os separados por vírgula, com no exemplo a seguir:

...
email("seu_email@gmail.com, outro_email@hotmail.com",
"Trabalho Android",
"Olá Thiengo\n\n")
...

 

Lembrando que o segundo e terceiro argumentos são opcionais e respectivamente são: o assunto e corpo de email.

A sintaxe com vírgula, "seu_email@gmail.com, outro_email@hotmail.com", na verdade é interpretada corretamente pelo aplicativo de email escolhido, esse é que entende que há mais de um email na String.

Isso, pois a interface Anko para email() somente trabalha com uma String no primeiro parâmetro. Mesmo sabendo que internamente será utilizado um arrayOf(), nativo do Kotlin, esse método não tema funcionalidade de quebrar a String e assim criar itens de array devido ao uso de vírgulas.

Executando o aplicativo em um device que tem ao menos um app de emails, logo depois clicando em "Envie um e-mail", temos:

Envio de e-mail por meio de uma Intent Android

Caso você queira utilizar a função email(), porém fornecendo somente o endereço de email e o corpo do email, você pode utilizar uma característica do Kotlin chamada "argumentos nomeados". Veja como  é a assinatura de email():

...
fun Context.email(email: String, subject: String = "", text: String = ""): Boolean {
...
}
...

 

Temos os parâmetros email, subject e text. Para trabalhar com a característica de "argumentos nomeados" devemos invocar a função colocando nela, junto ao valor do argumento, o rótulo do argumento:

...
email(email = "seu_email@gmail.com",
text = "Olá\n\n")
...

 

Assim teremos a invocação de um aplicativo de email que receberá o endereço para envio e o corpo do email, o usuário ainda terá de fornecer o assunto desse.

Interface para compartilhamento de conteúdo

Ainda no método onNavigationItemSelected(), adicione o seguinte código em destaque:

...
override fun onNavigationItemSelected(item: MenuItem): Boolean {
val id = item.itemId

if (id == R.id.nav_email) {
email("thiengoclopsita@gmail.com",
"Trabalho Android",
"Olá\n\n")
}
else if (id == R.id.nav_compartilhar) {
share("https://www.thiengo.com.br", "Thiengo [Calopsita]")
}

val drawer = findViewById(R.id.drawer_layout) as DrawerLayout
drawer.closeDrawer(GravityCompat.START)
return true
}
...

 

Executando o aplicativo em um device que contém apps que permitam o compartilhamento de conteúdo, logo depois clicando em "Compartilhar ideia", temos:

Compartilhamento de conteúdo por meio de uma Intent Android

Lembre que o segundo argumento, o referente ao assunto do compartilhamento, é opcional e alguns aplicativos não o utilizam, por exemplo: o Google Plus, que foi o utilizado nos testes aqui.

Interface para acesso ao site da instituição

Para que o usuário cliente também possa conhecer o site da empresa, no método onNavigationItemSelected(), adicione o seguinte código destacado:

...
override fun onNavigationItemSelected(item: MenuItem): Boolean {
val id = item.itemId

if (id == R.id.nav_email) {
email("thiengoclopsita@gmail.com",
"Trabalho Android",
"Olá\n\n")
}
else if (id == R.id.nav_compartilhar) {
share("https://www.thiengo.com.br", "Thiengo [Calopsita]")
}
else if (id == R.id.nav_site) {
browse("https://www.thiengo.com.br")
}

val drawer = findViewById(R.id.drawer_layout) as DrawerLayout
drawer.closeDrawer(GravityCompat.START)
return true
}
...

 

Executando o aplicativo em um device ou emulador que tem um aplicativo de navegador, logo depois clicando em "Visitar site", temos:

Abrindo um site a partir de uma Intent Android

Abertura de atividade e interface de solicitação de envio de SMS

Para a funcionalidade de envio de SMS, primeiro temos de ter o código de abertura da atividade SMSActivity, logo, em onNavigationItemSelected(), adicione o seguinte código em destaque:

...
override fun onNavigationItemSelected(item: MenuItem): Boolean {
val id = item.itemId

if (id == R.id.nav_sms) {
startActivity<SMSActivity>( "inicio_texto" to "Olá" )
}
else if (id == R.id.nav_email) {
email("thiengoclopsita@gmail.com",
"Trabalho Android",
"Olá")
}
else if (id == R.id.nav_compartilhar) {
share("https://www.thiengo.com.br", "Thiengo [Calopsita]")
}
else if (id == R.id.nav_site) {
browse("https://www.thiengo.com.br")
}

val drawer = findViewById(R.id.drawer_layout) as DrawerLayout
drawer.closeDrawer(GravityCompat.START)
return true
}
...

 

Agora, em SMSActivity, coloque os seguintes códigos em destaque:

...
override fun onCreate(savedInstanceState: Bundle?) {
...
et_mensagem.text = Editable
.Factory
.getInstance()
.newEditable( intent.getStringExtra("inicio_texto") )
}
...

fun enviarSms( view: View? ){
sendSMS( "999887766", et_mensagem.text.toString())
}
...

 

Veja que foi necessário utilizarmos um Factory do Editable, pois esse é o tipo de dado esperado dentro de um EditText e o que tínhamos no Intent era uma String, tipo que não implementa um Editable.

Logo, a opção de uso com o cast explicito, como: intent.getStringExtra("inicio_texto") as Editable. Esta opção não funcionaria, pois o cast neste caso é impossível.

A função enviarSms() tem um parâmetro View para ser válido como método de listener de clique no Android, mesmo que este parâmetro não seja utilizado ele deve estar presente.

Agora, no XML de /res/layout/content_sms.xml, temos de colocar a referência ao método de listener de clique:

...
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
android:onClick="enviarSms"
android:text="@string/enviar_button" />
...

 

Executando o aplicativo em um device ou emulador com app de mensagens SMS, logo depois clicando em "Envie um SMS" e assim prosseguindo com o envio, temos:

Executando um aplicativo de SMS a partir do acionamento de uma Intent Android

Note que todas as vezes que clicamos em um item do NavigationDrawer ele é destacado como selecionado, porém em nossa lógica de negócio somente um conteúdo permanecerá em tela na atividade que contém o Navigation, logo, vamos a uma atualização em onNavigationItemSelected() para que sempre o primeiro item se mantenha selecionado:

...
override fun onNavigationItemSelected(item: MenuItem): Boolean {
...
return true
}
...

Interface para chamada telefônica

Antes de colocarmos a simples invocação ao método Anko Commons, makeCall(). Devemos primeiro colocar a permissão CALL_PHONE no AndroidManifest.xml para versões do Android inferiores a versão 23, Marshmallow:

<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="br.com.thiengo.thiengoinstitucionalapp">

<uses-permission android:name="android.permission.CALL_PHONE" />

...
</manifest>

 

Agora, para que seja possível a invocação bem sucedida de makeCall() também em devices com o Android 6 ou superior, precisamos do algoritmo de solicitação de permissão em tempo de execução.

Para isso, aqui vamos utilizar a library ReactivePermissions de Max Cruz: https://github.com/MaxCruz/reactive_permissions.

Na MainActivity, coloque os códigos de inicialização da API de permissão:

class MainActivity :
AppCompatActivity(),
NavigationView.OnNavigationItemSelectedListener {

val REQUEST_CODE = 554
val reacPermissions = ReactivePermissions(this, REQUEST_CODE)
...
}

 

Assim podemos ir a definição do objeto de permissão, ainda na MainActivity, em um novo método:

...
private fun makePhoneCall(){
val phone = Permission(
Manifest.permission.CALL_PHONE, /* A PERMISSÃO QUE SERÁ SOLICITADA */
R.string.explicacao_permissao, /* TEXTO QUE É EXIBIDO NA SEGUNDA TENTATIVA, CASO O USÁRIO NEGUE A PERMISSÃO */
true /* CASO O USUÁRIO VENHA NEGAR A PERMISSÃO, ELE AINDA PODERÁ PERMANECER UTILIZANDO O APLICATIVO */
)
val permissions = listOf(phone)

reacPermissions.observeResultPermissions().subscribe{
event ->
if (event.second) {
makeCall("999887766")
}
}
reacPermissions.evaluate(permissions)
}
...

 

O primeiro argumento de Permission é o indicador da permissão que estaremos solicitando. O segundo argumento é um texto explicativo que aparecerá ao usuário caso ele negue a permissão.

O terceiro argumento, true ou false, indica que o aplicativo pode continuar, quando true, caso o usuário negue a permissão. Ou não deve continuar, quando false. Neste último caso, se o usuário negar a permissão, um dialog travará a continuação do uso do app.

O método Kotlin listOf() retorna uma lista imutável dos objetos passados como argumento. Podemos ter mais de uma permissão quando utilizando a library ReactivePermissions.

Ok, mas o que significa o código a seguir?

...
event ->
if (event.second) {
makeCall("999887766")
}
...

 

Essa é a sintaxe de um Lambda na linguagem Kotlin. Caso ainda não conheça, um Lambda, de forma resumida, é uma função anônima, algo útil em muitas situações, principalmente quando o código é simples e pode ser declarado na mesma linha onde ele é necessário.

Para o Kotlin, a sintaxe é a seguinte:

  • A expressão Lambda é sempre envelopada por chaves, {};
  • Caso haja parâmetros, esses vêm antes da seta, ->;
  • O corpo do Lambda vem depois da seta, ->, isso caso haja parâmetros, senão somente coloque o corpo do Lambda dentro das chaves, {};

Como já mencionado aqui no Blog, mais precisamente no primeiro artigo sobre o Kotlin: é possível ter variantes de determinadas APIs nessa linguagem.

Uma outra maneira de trabalhar o Lambda no código de permissão anterior é como segue:

...
reacPermission.observeResultPermissions().subscribe( {
event -> run{
if (event.second) {
makeCall("999887766")
}
}
} )
...

 

Assim fica até mais didático, mas saiba que o Kotlin, como qualquer outra linguagem de programação, tem convenções, e uma delas diz que caso o Lambda seja o último argumento de uma função, aqui a função é a subscribe(), ele deve vir fora dos parênteses:

...
reacPermission.observeResultPermissions().subscribe(){
event ->run {
if (event.second) {
makeCall("999887766")
}
}
}
...

 

Uma outra convenção diz que caso o Lambda seja o único argumento da função, os parênteses podem ser omitidos:

...
reacPermission.observeResultPermissions().subscribe{
event ->
if (event.second) {
makeCall("999887766")
}
}
...

 

Removemos o run{}, pois ele é opcional neste caso, com apenas um bloco de código.

A library ReactivePermissions trabalha com o paradigma Reactive Programming, ou seja, faz uso do padrão Observer. Por isso temos um método subscribe().

O que estamos fazendo, passando um Lambda como argumento para o subscribe(), é inscrevendo um observador para cada resposta de solicitação de permissão que o usuário dê em nosso aplicativo.

O event.second é para saber se a permissão atual em invocação foi ou não concedida, true ou false. Caso em seu domínio do problema houvesse acionamentos distintos de acordo com a permissão liberada, e você estivesse utilizando a mesma library de permissions deste projeto de exemplo, uma maneira de trabalhar o Lambda, seria:

...
event ->
if (event.first == Manifest.permission.CALL_PHONE
&& event.second) {

makeCall("999887766")
}
else if (event.first == Manifest.permission.SEND_SMS
&& event.second) {
sendSMS("999887766")
}
...

 

Com isso, o que nos resta é colocar a invocação de makePhoneCall() no local correto e então sobrescrevermos o método onRequestPermissionsResult() da atividade para podermos passar a resposta do usuário a propriedade reacPermissions para posterior processamento em nosso Lambda.

Segue código atualizado:

...
override fun onNavigationItemSelected(item: MenuItem): Boolean {
val id = item.itemId

if (id == R.id.nav_contato_urgente) {
makePhoneCall()
}
else if (id == R.id.nav_sms) {
startActivity<SMSActivity>("inicio_texto" to "Olá\n\n")
}
else if (id == R.id.nav_email) {
email("thiengoclopsita@gmail.com",
"Trabalho Android",
"Olá")
}
else if (id == R.id.nav_compartilhar) {
share("https://www.thiengo.com.br", "Thiengo [Calopsita]")
}
else if (id == R.id.nav_site) {
browse("https://www.thiengo.com.br")
}

val drawer = findViewById(R.id.drawer_layout) as DrawerLayout
drawer.closeDrawer(GravityCompat.START)
return false
}
...

override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray) {

if (requestCode == REQUEST_CODE){
reacPermissions.receive(permissions, grantResults)
}
}
...

 

Note que quando sobrescrevendo o método onRequestPermissionsResult() o segundo parâmetro, permissions, tende a vir com a seguinte definição de tipo: Array<out String>. Definição que é equivalente a Array<? extends String>. Lembrando que String é final e não pode ser estendida.

Seguramente você pode remover o out, pois não haverá problemas com a sobrescrita de onRequestPermissionsResult() e assim a invocação a receive() será permitida.

Com isso seguramente podemos realizar testes de chamadas telefônicas pela interface Anko Commons até mesmo em aplicativos com a Android API 23 ou superior.

Execute o app em um device ou emulador, em seguida clique em "Contato urgente" e assim terá algo como:

Abrindo o aplicativo Android de telefonema por meio de uma Intent Anko

Assim terminamos nosso primeiro projeto de apresentação com a biblioteca Anko, mais precisamente, o Anko Commons. Vamos continuar trabalhando com o Kotlin e ainda mais APIs desta linguagem.

Não esqueça de se inscrever na lista de e-mails 📩 do Blog, ao lado ou ao final do artigo, para receber os conteúdos em primeira mão.

Inscreva-se também no canal no YouTube: Thiengo Calopsita.

Vídeo com implementação passo a passo do projeto

A seguir o vídeo com a implementação passo a passo do Anko Commons no projeto Kotlin de exemplo:

Para acesso ao conteúdo completo do projeto, entre no seguinte GitHub: https://github.com/viniciusthiengo/thiengo-institucional-app-kotlin.

Conclusão

Sabendo da simplicidade do Kotlin para desenvolvimento de aplicativos Android, isso por essa ser uma linguagem nova e com características modernas de desenvolvimento, o JetBrains mesmo assim criou o Anko para facilitar ainda mais a codificação.

Com o Anko Commons conseguimos utilizar wrappers para as principais APIs do Android SDK. APIs como: Intent, Dialog, Toast, cálculo e conversão de dimensão além do LogCat ou Logging.

Ainda é possível a construção de layouts dinâmicos, gerenciamento facilitado do SQLite e trabalho com o Kotlin.coroutines.

Um ponto negativo em relação a divulgação do Anko é que na documentação do Kotlin (em inglês), mesmo sendo também o JetBrains a entidade criadora da library, na doc não há alguma parte dedicada a essa biblioteca. Pode ser que em versões futuras ela seja colocada. De qualquer forma, o GitHub da library é bem completo.

Em artigos futuros sobre o Kotlin, manteremos os estudos do Anko, algumas partes ainda não discutidas neste artigo.

Não deixe de comentar o que achou ou suas dúvidas e sugestões e também de se inscrever na 📩 lista de e-mails do Blog logo abaixo.

Abraço.

Fontes

Anko Kotlin, página oficial

Kotlin Reference: Null Safety

Kotlin Reference: Functions

Kotlin Reference: Lambda

Stack Overflow: What is a lambda (function)?

Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba grátis conteúdos Android sem precedentes!
Email inválido

Relacionado

FlexboxLayout Para Um Design Previsível No AndroidFlexboxLayout Para Um Design Previsível No AndroidAndroid
Como Utilizar Spannable no Android Para Customizar StringsComo Utilizar Spannable no Android Para Customizar StringsAndroid
FCM Android - Domínio do Problema, Implementação e Testes Com Servidor de Aplicativo [Parte 1]FCM Android - Domínio do Problema, Implementação e Testes Com Servidor de Aplicativo [Parte 1]Android
Kotlin Android, Entendendo e Primeiro ProjetoKotlin Android, Entendendo e Primeiro ProjetoAndroid

Compartilhar

Comentários Facebook

Comentários Blog

Para código / script, coloque entre [code] e [/code] para receber marcação especifica.
Forneça seu nome válido.
Forneça seu email válido.
Forneça o comentário.
Enviando, aguarde...