Redes Sociais
 Telegram  YouTube
Como criar uma Calculadora em kotlin com ViewModel
27 de setembro de 2020

Neste tutorial irei descrever como criar uma calculadora simples de quatro operações em kotlin mas vamos usar o recurso de ViewModel.

Antes de mais nada se desejar saber um pouco mais sobre ViewModel visite este site.

Então vamos começar o nosso tutorial criando um projeto novo vazio(Empty) com o nome de br.com.uware.calculadoraviewmodel .

Layout

Logo após ter criado o projeto vamos fazer a base do nosso projeto com o style e cores que vamos utilizar para a nossa calculadora.

Primeiramente vamos acertar o nosso layout para que funciona da maneira que queremos e para isso vamos modificar um pouco o arquivo res/styles.xml deixando ele da seguinte maneira.

<resources>
    <!-- Base application theme. 
        Tema sem ActionBar -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>

        <!-- Retira espaço entre os botões -->
        <item name="android:buttonStyle">@style/Widget.AppCompat.Button.Borderless</item>
    </style>

</resources>

Com o nosso style pronto vai ser modificada as cores que vamos utilizar no projeto editando dessa forma o arquivo res/colors.xml.

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#6200EE</color>
    <!-- Modificada cor PrimaryDark -->
    <color name="colorPrimaryDark">#3E6A26</color>
    <color name="colorAccent">#03DAC5</color>

    <!-- Cores que serão utilizadas -->
    <color name="black">#000</color>
    <color name="white">#FFF</color>
    <color name="gray">#4D4D53</color>
    <color name="darkgray">#302B2B</color>
    <color name="green">#3E6A26</color>
</resources>

Agora chegou a hora de inserir um ícone para assim ser usado no botão de apagar com o nome de ic_back.xml dentro da pasta drawable.





Com o ícone na pasta drawable então chegou a hora de criar nosso arquivo de layout activity_main.xml para o projeto.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:background="@color/black"
tools:context=".MainActivity">
<TextView
android:id="@+id/tvCalc"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:textColor="@color/white"
android:textSize="30dp"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/tvNumbers"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:maxLength="24"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:text="0"
android:textColor="@color/white"
android:textSize="55dp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="70dp"
android:orientation="horizontal">
<Button
android:id="@+id/btnAC"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/darkgray"
android:textColor="@color/white"
android:textSize="28dp" />
<Button
android:id="@+id/btnDivide"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/darkgray"
android:textColor="@color/white"
android:textSize="28dp" />
<Button
android:id="@+id/btnMultiplica"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/darkgray"
android:textColor="@color/white"
android:textSize="28dp" />
<ImageButton
android:id="@+id/btnLimpa"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/darkgray"
android:src="@drawable/ic_back" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="70dp"
android:orientation="horizontal">
<Button
android:id="@+id/btnNum7"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/gray"
android:textColor="@color/white"
android:textSize="28dp" />
<Button
android:id="@+id/btnNum8"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/gray"
android:textColor="@color/white"
android:textSize="28dp" />
<Button
android:id="@+id/btnNum9"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/gray"
android:textColor="@color/white"
android:textSize="28dp" />
<Button
android:id="@+id/btnMenos"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/darkgray"
android:textColor="@color/white"
android:textSize="28dp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="70dp"
android:orientation="horizontal">
<Button
android:id="@+id/btnNum4"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/gray"
android:textColor="@color/white"
android:textSize="28dp" />
<Button
android:id="@+id/btnNum5"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/gray"
android:textColor="@color/white"
android:textSize="28dp" />
<Button
android:id="@+id/btnNum6"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/gray"
android:textColor="@color/white"
android:textSize="28dp" />
<Button
android:id="@+id/btnMais"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/darkgray"
android:textColor="@color/white"
android:textSize="28dp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="140dp"
android:orientation="horizontal">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="140dp"
android:orientation="vertical"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="70dp"
android:orientation="horizontal">
<Button
android:id="@+id/btnNum1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/gray"
android:textColor="@color/white"
android:textSize="28dp" />
<Button
android:id="@+id/btnNum2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/gray"
android:textColor="@color/white"
android:textSize="28dp" />
<Button
android:id="@+id/btnNum3"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/gray"
android:textColor="@color/white"
android:textSize="28dp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="70dp"
android:orientation="horizontal">
<Button
android:id="@+id/btnNum0"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/gray"
android:textColor="@color/white"
android:textSize="28dp" />
<Button
android:id="@+id/btnPonto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/gray"
android:textColor="@color/white"
android:textSize="28dp" />
<Button
android:id="@+id/btnSinal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@color/gray"
android:textColor="@color/white"
android:textSize="28dp" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="140dp"
android:orientation="horizontal"
android:layout_weight="3">
<Button
android:id="@+id/btnIgual"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:layout_gravity="center"
android:background="@color/green"
android:textColor="@color/white"
android:textSize="28dp"/>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>




Programação

Para a parte da programação vamos começar mudando o nosso AndroidManifest.xml para que ele utilize apenas a orientação de portrait deixando ele assim como da seguinte maneira.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="br.com.uware.calculadoraviewmodel">
<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">
<!-- Orientação em Portrait -->
<activity android:name=".MainActivity"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

Agora vamos então modificar o nosso gradle(module: app) para que tenha as bibliotecas que vamos usar no aplicativo, para isso teremos que deixar como o seguinte.

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
// Necessário para a função viewModels() da activity-ktx
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
kotlinOptions {
jvmTarget = "1.8"
}
// Fim da inclusão do target da jvm da activity-ktx
defaultConfig {
applicationId "br.com.uware.calculadoraviewmodel"
minSdkVersion 24
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
// Versão do lifecycle ViewModel e activity-ktx
def lifecycle_version = "2.2.0"
def activity_version = "1.1.0"
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.1'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
// ViewModel e activity-ktx
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
implementation "androidx.activity:activity-ktx:$activity_version"
// Expression Builder
implementation 'net.objecthunter:exp4j:0.4.8'
}

Logo após modificar sincronize(Sync) no ícone no canto superior direito para que seja atualizado.

Adicionamos assim as bibliotecas viewmodel, activity-ktx e exp4j.





Como criar uma Calculadora em kotlin com ViewModel

Portanto agora chegou a hora de criar a calculadora e para isso vamos começar criando dois packages com o nome de constants e model.

Nesta parte primeiramente vamos criar um arquivo com o nome de Constants.kt dentro do nosso package constants, que é onde vai ficar as constantes que vamos usar em nossa calculadora com o seguinte conteúdo.

package br.com.uware.calculadoraviewmodel.constants
/**
*   Author:     Rodrigo Leutz
*   Project:    Calculadora ViewModel
*   Date:       26/09/20
*/
const val N0 = "0"
const val N1 = "1"
const val N2 = "2"
const val N3 = "3"
const val N4 = "4"
const val N5 = "5"
const val N6 = "6"
const val N7 = "7"
const val N8 = "8"
const val N9 = "9"
const val POINT = "."
const val SIGNAL = "+/-"
const val MINUS = "-"
const val PLUS ="+"
const val DIVIDE = "÷"
const val MULTIPLY = "×"
const val AC = "AC"
const val BACK = "BACK"
const val EQUAL = "="
const val ERROR = "Error"

Logo após vamos então criar o model que vamos usar para o visor de nossa calculadora, então vamos criar uma classe dentro do nosso package model com o nome de CalcVisor.kt .

E vamos então deixa-la com o seguinte código.

package br.com.uware.calculadoraviewmodel.model
/**
*   Author:     Rodrigo Leutz
*   Project:    Calculadora ViewModel
*   Date:       26/09/20
*/
data class CalcVisor(
var number: String = "",
var calc: String = ""
)

Portanto com nosso modelo e constantes criadas chegou a hora de criar o nosso ViewModel.

Então para isso vamos criar uma classe com o nome de MainViewModel.kt e inserir o código que ele vai usar para fazer as operações.

O código deve estar igualmente ao seguinte.

package br.com.uware.calculadoraviewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import br.com.uware.calculadoraviewmodel.constants.*
import br.com.uware.calculadoraviewmodel.model.CalcVisor
import net.objecthunter.exp4j.ExpressionBuilder
/**
*   Author:     Rodrigo Leutz
*   Project:    Calculadora ViewModel
*   Date:       26/09/20
*/
class MainViewModel : ViewModel() {
private val visor = CalcVisor()
private var equalPress = false
private var hasError = false
private val calcVisor: MutableLiveData<CalcVisor> by lazy {
MutableLiveData<CalcVisor>()
}
// Envia a variável calcVisor
fun getNumbers(): LiveData<CalcVisor> {
return calcVisor
}
// Recebe o click da tela
fun setClick(c: Any?) {
if (hasError) {
if (c == AC) processClick(AC)
} else processClick(c.toString())
}
// Processa informação do click
private fun processClick(c: String) {
when (c) {
AC -> resetCalc()
BACK -> {
if (visor.number.length > 1) {
visor.number = visor.number.substring(0, visor.number.length - 1)
} else {
visor.number = N0
}
}
POINT -> {
if (!visor.number.contains(POINT)) {
if (visor.number == "") visor.number = "$N0." else visor.number += "$c"
}
}
SIGNAL -> {
visor.number =
if (visor.number.isNotEmpty() && visor.number.first() == MINUS.first()) {
visor.number.substring(1, visor.number.length)
} else {
"$MINUS${visor.number}"
}
}
PLUS -> setCalc(PLUS)
MINUS -> setCalc(MINUS)
MULTIPLY -> setCalc(MULTIPLY)
DIVIDE -> setCalc(DIVIDE)
EQUAL -> {
visor.calc += visor.number
makeCalc(visor.calc)
}
else -> {
try {
c.toInt()
when(visor.number){
N0 -> visor.number = ""
"$MINUS$N0" -> visor.number = "$MINUS"
}
visor.number += "$c"
} catch (e: Exception) {
hasError = true
visor.number = ERROR
}
}
}
calcVisor.value = visor
}
// Função AC da calculadora
private fun resetCalc() {
hasError = false
equalPress = false
visor.calc = ""
visor.number = N0
}
// Adiciona o cálculo a ser feito no visor.calc
private fun setCalc(str: String) {
if (equalPress) {
visor.calc = ""
equalPress = false
}
visor.calc += "${visor.number}${str}"
visor.number = N0
}
// Mostra o resultado apertando " = "
private fun makeCalc(str: String) {
var result: String
try {
val replaceStr = str.replace(PLUS, "+")
.replace(MINUS, "-")
.replace(DIVIDE, "/")
.replace(MULTIPLY, "*")
val calc = ExpressionBuilder(replaceStr).build()
result = calc.evaluate().toString()
visor.calc += EQUAL
} catch (e: Exception) {
hasError = true
result = ERROR
}
visor.number =
if (result.substring(
result.length - 2,
result.length
) == "$POINT$N0"
) result.substring(
0,
result.length - 2
) else result
equalPress = true
}
}




Logo após ter inserido o código de nosso ViewModel chegou a parte final de criar as funções visuais de nossa calculadora.

Vamos editar o arquivo MainActivity.kt e deixa-lo igualmente ao seguinte.

package br.com.uware.calculadoraviewmodel
import android.os.Bundle
import android.view.View
import android.widget.Button
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import br.com.uware.calculadoraviewmodel.constants.*
import br.com.uware.calculadoraviewmodel.model.CalcVisor
import kotlinx.android.synthetic.main.activity_main.*
/**
*   Author:     Rodrigo Leutz
*   Project:    Calculadora ViewModel
*   Date:       26/09/20
*/
class MainActivity : AppCompatActivity() {
// Iniciando o ViewModel
private val model: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Lista de botões da calculadora
val listButton = listOf<View>(
btnNum0, btnNum1, btnNum2, btnNum3,
btnNum4, btnNum5, btnNum6, btnNum7,
btnNum8, btnNum9, btnPonto, btnAC,
btnLimpa, btnMais, btnMenos, btnMultiplica,
btnDivide, btnIgual, btnSinal
)
// Seta o nome dos botões de acordo com as constantes pela função
for (x in listButton) {
if(x is Button){
x.text = getBtnConstant(x).toString()
}
}
// Setando função de clique para todos os botões
for (x in listButton) {
x.click()
}
// Setando viewmodel para receber as Strings
// de cálculo e números
model.getNumbers().observe(this, Observer<CalcVisor> {
tvNumbers.text = it.number
tvCalc.text = it.calc
})
}
// Pega o valor da constante do botão
private fun getBtnConstant(any: Any): Any? {
return when(any) {
btnNum0 -> N0
btnNum1 -> N1
btnNum2 -> N2
btnNum3 -> N3
btnNum4 -> N4
btnNum5 -> N5
btnNum6 -> N6
btnNum7 -> N7
btnNum8 -> N8
btnNum9 -> N9
btnPonto -> POINT
btnSinal -> SIGNAL
btnLimpa -> BACK
btnAC -> AC
btnMais -> PLUS
btnMenos -> MINUS
btnMultiplica -> MULTIPLY
btnDivide -> DIVIDE
btnIgual -> EQUAL
else -> null
}
}
// Inserindo o Listener no Botão
private fun View.click() {
this.setOnClickListener {
send(it)
}
}
// Função que envia o botão clicado
private fun send(v: View) {
// Seta o valor da variável c de acordo com a tecla clicada
val c = getBtnConstant(v)
if (c != null) {
model.setClick(c)
}
}
}

Agora chegou a hora de compilar e rodar, se tudo ocorreu como esperado é para você ver uma calculadora como a seguinte.

Enfim espero poder ter ajudado com mais este tutorial sobre como criar uma Calculadora em kotlin com ViewModel.

Autor: Rodrigo Leutz

Desenvolvedor Web e Android ( Kotlin e Java )


Pegar endereço com CEP usando coroutines e retrofit em Kotlin

Nesse tutorial vou mostrar como fazer um aplicativo em MVVM para pegar endereço com CEP usando coroutines e retrofit em[...]

4 de novembro de 2020

Converter Binário para Inteiro e Inteiro para Binário em Kotlin

Nesse tutorial vamos portanto ver como converter número binário para inteiro e também inteiro para binário em Kotlin no Android[...]

29 de outubro de 2020

Problema com Kotlin e synthetic no Android Studio

Notei que tem algumas pessoas com problemas com kotlin e synthetic no Android Studio então escrevi esta dica para solução desse[...]

27 de outubro de 2020