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