Android Calculator App – Full Source Code
๐งฎ Android Calculator App Smart. Stylish. Super Fast. This Android Calculator app offers a sleek, modern interface with real-time calculations and a custom UI for seamless math operations. Designed to be both functional and beautiful, it goes beyond basic calculations to offer advanced features and a smooth user experience. โจ Features: ๐ข Real-time expression evaluation ๐ง Smart memory functions (MC, MR, M+, Mโ) ๐พ Auto-saving of all calculations (persisted with history, even after closing the app) ๐งฎ Supports brackets, decimals, and order of operations ๐จ Custom button styles and layout using GridLayout ๐ Dark theme UI with white text for eye comfort ๐ Calculation history screen ๐ฒ AdMob integration for banner and interstitial ads
Import these into build.gradle.kts
implementation(libs.exp4j)
implementation(libs.play.services.ads)
Paste into lib.version.toml
[versions]
playServicesAds = "23.6.0" <!-- Check for latest version -->
exp4j = "0.4.8" <!-- Check for latest version -->
Paste into lib.version.toml
[libraries]
play-services-ads = { group = "com.google.android.gms", name = "play-services-ads", version.ref = "playServicesAds" }
exp4j = { group = "net.objecthunter", name = "exp4j", version.ref = "exp4j" }
Paste into Color.xml
<resources>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="navi">#071A28</color>
<color name="red">#E53935</color>
<color name="yellow">#F8B822</color>
</resources>
Paste into AndroidManifest
<meta-data
android:name="com.google.android.gms.ads.APPLICATION_ID"
android:value="ca-app-pub-3940256099942544~3347511713">
Create a new vector Asset in Drawable : baseline_arrow_back_24 And baseline_history_24
MainActivity Layout (XML)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@color/black"
android:orientation="vertical">
<ImageButton
android:id="@+id/historyButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:src="@drawable/baseline_history_24" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/black"
android:layout_marginTop="20dp"
android:orientation="vertical">
<EditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="1sp"
android:layout_marginBottom="20dp"
android:background="@drawable/EdittextBkg"
android:hint="0 "
android:text="0"
android:textColorHint="@color/white"
android:textAlignment="textEnd"
android:textColor="@color/white"
android:textSize="30sp" />
<!-- First Row -->
<LinearLayout
android:layout_marginTop="35dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="10dp">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/backspace"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:background="@drawable/numberbkg"
android:text="C"
android:textColor="@color/white"
android:textSize="25sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/buttonBr1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:background="@drawable/numberbkg"
android:text="("
android:textColor="@color/white"
android:textSize="25sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/buttonBr2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:background="@drawable/numberbkg"
android:text=")"
android:textColor="@color/white"
android:textSize="25sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/buttonDivide"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/numberbkg"
android:text="/"
android:textColor="@color/white"
android:textSize="25sp"
android:textStyle="bold" />
</LinearLayout>
<!-- Row 2 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="10dp">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/button1" android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="8dp" android:layout_weight="1"
android:background="@drawable/numberbkg"
android:text="1" android:textColor="@color/white"
android:textSize="25sp" android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/button2" android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:background="@drawable/numberbkg"
android:text="2"
android:textColor="@color/white"
android:textSize="25sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/button3" android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:background="@drawable/numberbkg"
android:text="3"
android:textColor="@color/white"
android:textSize="25sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/buttonAdd"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/numberbkg"
android:text="+"
android:textColor="@color/white"
android:textSize="25sp"
android:textStyle="bold" />
</LinearLayout>
<!-- Row 3 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="10dp">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/button4"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:textColor="@color/white"
android:background="@drawable/numberbkg"
android:text="4"
android:textSize="25sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/button5"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:textColor="@color/white"
android:background="@drawable/numberbkg"
android:text="5"
android:textSize="25sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/button6"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:textColor="@color/white"
android:background="@drawable/numberbkg"
android:text="6"
android:textSize="25sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/buttonSubtract"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/numberbkg"
android:text="-" android:textColor="@color/white"
android:textSize="25sp"
android:textStyle="bold" />
</LinearLayout>
<!-- Row 4 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="10dp">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/button7"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:background="@drawable/numberbkg"
android:text="7"
android:textColor="@color/white"
android:textSize="25sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/button8"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:background="@drawable/numberbkg"
android:text="8"
android:textColor="@color/white"
android:textSize="25sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/button9"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:background="@drawable/numberbkg"
android:text="9"
android:textColor="@color/white"
android:textSize="25sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/buttonMultiply"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/numberbkg"
android:text="*"
android:textColor="@color/white"
android:textSize="25sp"
android:textStyle="bold" />
</LinearLayout>
<!-- Row 5 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="10dp">
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/AC"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:textColor="@color/white"
android:background="@drawable/numberbkg"
android:text="AC"
android:textSize="25sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/button0"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:textColor="@color/white"
android:background="@drawable/numberbkg"
android:text="0"
android:textSize="25sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/buttonDot"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginRight="8dp"
android:layout_weight="1"
android:background="@drawable/numberbkg"
android:text="."
android:textColor="@color/white"
android:textSize="25sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/buttonEquals"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/numberbkg"
android:text="=" android:textColor="@color/white"
android:textSize="25sp"
android:textStyle="bold" />
</LinearLayout>
</LinearLayout>
<com.google.android.gms.ads.AdView
android:id="@+id/adView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginTop="105dp"
android:layout_marginBottom="16dp"
app:adSize="SMART_BANNER"
app:adUnitId="ca-app-pub-3940256099942544/9214589741" />
</LinearLayout>
MainActivity.kt
import android.content.Intent
import android.graphics.PorterDuff
import android.os.Bundle
import android.text.Editable
import android.text.Spannable
import android.text.SpannableString
import android.text.TextWatcher
import android.text.style.ForegroundColorSpan
import android.text.style.RelativeSizeSpan
import android.util.Log
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.ImageButton
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.google.android.gms.ads.AdListener
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.AdView
import com.google.android.gms.ads.FullScreenContentCallback
import com.google.android.gms.ads.LoadAdError
import com.google.android.gms.ads.MobileAds
import com.google.android.gms.ads.interstitial.InterstitialAd
import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback
import net.objecthunter.exp4j.ExpressionBuilder
class MainActivity : AppCompatActivity() {
private lateinit var editText: EditText
private var interstitialAd: InterstitialAd? = null
private lateinit var imageButton: ImageButton
private var bannerAd: AdView? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
loadInterstitialAd()
editText = findViewById(R.id.editText)
setupInputChangeListener()
bannerAd = findViewById(R.id.adView)
MobileAds.initialize(this) {}
loadBannerAd()
val buttonIds = arrayOf(
R.id.button0, R.id.button1, R.id.button2, R.id.button3, R.id.button4,
R.id.button5, R.id.button6, R.id.button7, R.id.button8, R.id.button9,
R.id.buttonDot, R.id.buttonAdd, R.id.buttonSubtract, R.id.buttonMultiply,
R.id.buttonDivide, R.id.buttonBr1, R.id.buttonBr2, R.id.backspace,
R.id.AC, R.id.buttonEquals
)
for (buttonId in buttonIds) {
val button = findViewById<Button>(buttonId)
styleButton(button, buttonId)
button.setOnClickListener { onButtonClick(it) }
}
findViewById<Button>(R.id.buttonEquals).setOnClickListener { onEqualsButtonClick() }
findViewById<ImageButton>(R.id.historyButton).setOnClickListener {
if (interstitialAd != null) {
interstitialAd?.show(this)
} else {
navigateToHistory()
}
}
}
private fun loadInterstitialAd() {
val adRequest = AdRequest.Builder().build()
InterstitialAd.load(
this,
"ca-app-pub-3940256099942544/1033173712",
adRequest,
object : InterstitialAdLoadCallback() {
override fun onAdLoaded(ad: InterstitialAd) {
interstitialAd = ad
interstitialAd?.fullScreenContentCallback =
object : FullScreenContentCallback() {
override fun onAdDismissedFullScreenContent() {
interstitialAd = null
navigateToHistory()
}
override fun onAdFailedToShowFullScreenContent(adError: com.google.android.gms.ads.AdError) {
navigateToHistory()
}
}
}
override fun onAdFailedToLoad(adError: LoadAdError) {
interstitialAd = null
}
}
)
}
private fun navigateToHistory() {
val intent = Intent(this, HistoryActivity::class.java)
startActivity(intent)
}
private fun styleButton(button: Button, buttonId: Int) {
val color = when (buttonId) {
in arrayOf(
R.id.buttonDot,
R.id.button0, R.id.button1, R.id.button2,
R.id.button3, R.id.button4, R.id.button5,
R.id.button6, R.id.button7, R.id.button8, R.id.button9
) -> R.color.navi
in arrayOf(
R.id.buttonEquals, R.id.buttonAdd,
R.id.buttonSubtract, R.id.buttonMultiply,
R.id.buttonDivide
) -> R.color.yellow
in arrayOf(R.id.backspace, R.id.AC) -> R.color.yellow
else -> R.color.yellow
}
val drawable = ContextCompat.getDrawable(this, R.drawable.numberbkg)
drawable?.setColorFilter(ContextCompat.getColor(this, color), PorterDuff.Mode.SRC)
button.background = drawable
}
private fun saveCalculation(expression: String, result: String) {
val operators = setOf("+", "-", "ร", "รท")
if (expression.any { it.toString() in operators }) {
val sharedPreferences = getSharedPreferences("CalculatorHistory", MODE_PRIVATE)
val editor = sharedPreferences.edit()
val historySet =
sharedPreferences.getStringSet("history", mutableSetOf()) ?: mutableSetOf()
historySet.add("$expression = $result")
editor.putStringSet("history", historySet)
editor.apply()
}
}
private fun setupInputChangeListener() {
editText.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
val expression = s.toString()
try {
val result = evaluateExpression(expression).toString()
saveCalculation(expression, result)
} catch (_: Exception) {
}
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
override fun afterTextChanged(s: Editable?) {}
})
}
private fun updateExpressionWithResult(expression: String) {
try {
val result = evaluateExpression(expression).toString()
val fullText = "$expression\n= $result"
val spannable = SpannableString(fullText)
val start = expression.length + 1
spannable.setSpan(
RelativeSizeSpan(0.8f),
start,
fullText.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
spannable.setSpan(
ForegroundColorSpan(ContextCompat.getColor(this, R.color.white)),
start, fullText.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
editText.setText(spannable)
editText.setSelection(expression.length)
} catch (e: Exception) {
}
}
private fun onButtonClick(view: View) {
val button = view as Button
val buttonText = button.text.toString()
var currentText = editText.text.toString().substringBefore("\n")
val operators = setOf("+", "-", "ร", "รท")
when (buttonText) {
"AC" -> editText.setText("")
"C" -> if (currentText.isNotEmpty()) editText.setText(currentText.dropLast(1))
"=" -> onEqualsButtonClick()
else -> {
if (currentText == "0") {
editText.setText(buttonText)
} else {
if (buttonText in operators) {
if (currentText.isNotEmpty() && operators.contains(currentText.last().toString())) {
editText.setText(currentText.dropLast(1) + buttonText)
} else {
editText.setText(currentText + buttonText)
}
} else {
editText.setText(currentText + buttonText)
}
}
}
}
}
private fun onEqualsButtonClick() {
val expression = editText.text.toString().substringBefore("\n")
try {
val result = evaluateExpression(expression).toString()
editText.setText("$expression\n= $result")
saveCalculation(expression, result)
editText.setSelection(editText.text.length)
} catch (e: Exception) {
editText.setText("Error")
}
}
private fun evaluateExpression(expression: String): String {
val result = ExpressionBuilder(expression).build().evaluate()
return if (result % 1 == 0.0) {
result.toInt().toString()
} else {
result.toString()
}
}
private fun loadBannerAd() {
val adRequest = AdRequest.Builder().build()
bannerAd?.loadAd(adRequest)
bannerAd?.adListener = object : AdListener() {
override fun onAdLoaded() {
Log.d("BannerAd", "Ad loaded successfully.")
}
override fun onAdFailedToLoad(adError: LoadAdError) {
Log.e("BannerAd", "Failed to load ad: ${adError.message}")
}
}
}
}
</code></pre>
HistoryActivity Layout (XML)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:orientation="vertical"
android:padding="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageButton
android:id="@+id/historyButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="0dp"
android:contentDescription="History"
android:src="@drawable/baseline_arrow_back_24" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="History"
android:layout_marginStart="90dp"
android:textColor="@color/white"
android:textSize="24sp"
android:textStyle="bold" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_marginTop="2dp"
android:layout_marginBottom="2dp"
android:background="#6B6666"
android:orientation="horizontal">
</LinearLayout>
<TextView
android:id="@+id/historyTextView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="start"
android:padding="8dp"
android:scrollbars="vertical"
android:text="History"
android:textColor="@color/white"
android:textSize="18sp" />
<Button
android:id="@+id/clearHistoryButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/yellow"
android:padding="12dp"
android:text="Clear History"
android:textColor="@color/black" />
</LinearLayout>
HistoryActivity.kt
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.widget.Button
import android.widget.ImageButton
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.app.AppCompatDelegate
import com.google.android.gms.ads.AdRequest
import com.google.android.gms.ads.FullScreenContentCallback
import com.google.android.gms.ads.LoadAdError
import com.google.android.gms.ads.interstitial.InterstitialAd
import com.google.android.gms.ads.interstitial.InterstitialAdLoadCallback
class HistoryActivity : AppCompatActivity() {
private lateinit var historyTextView: TextView
private lateinit var clearHistoryButton: Button
private lateinit var sharedPreferences: SharedPreferences
private var interstitialAd: InterstitialAd? = null
private lateinit var imageButton: ImageButton
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_history)
// Initialize views
historyTextView = findViewById(R.id.historyTextView)
clearHistoryButton = findViewById(R.id.clearHistoryButton)
imageButton = findViewById(R.id.historyButton) // Assign ImageButton correctly
sharedPreferences = getSharedPreferences("CalculatorHistory", MODE_PRIVATE)
loadHistory() // Load saved calculations
loadInterstitialAd() // Load AdMob ad
// Click listener for Clear History button
clearHistoryButton.setOnClickListener {
clearHistory()
}
// Click listener for ImageButton to show ad before navigation
imageButton.setOnClickListener {
if (interstitialAd != null) {
interstitialAd?.show(this) // Show the ad
} else {
navigateToHistory() // If ad is not ready, navigate directly
}
}
}
private fun loadInterstitialAd() {
val adRequest = AdRequest.Builder().build()
InterstitialAd.load(
this,
"ca-app-pub-5025863179121769/3076608569", // Replace with your actual Ad Unit ID
adRequest,
object : InterstitialAdLoadCallback() {
override fun onAdLoaded(ad: InterstitialAd) {
interstitialAd = ad
interstitialAd?.fullScreenContentCallback = object : FullScreenContentCallback() {
override fun onAdDismissedFullScreenContent() {
interstitialAd = null // Clear ad after it's closed
navigateToHistory() // Navigate after closing ad
}
override fun onAdFailedToShowFullScreenContent(adError: com.google.android.gms.ads.AdError) {
navigateToHistory() // If ad fails, navigate to history
}
}
}
override fun onAdFailedToLoad(adError: LoadAdError) {
interstitialAd = null // Ad failed to load
}
}
)
}
private fun navigateToHistory() {
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
}
private fun loadHistory() {
val historySet = sharedPreferences.getStringSet("history", emptySet()) ?: emptySet()
if (historySet.isEmpty()) {
historyTextView.text = "No history available"
} else {
historyTextView.text = historySet.joinToString("\n")
}
}
private fun clearHistory() {
sharedPreferences.edit().remove("history").apply()
historyTextView.text = "No history available"
}
}
Create a new resource file in Drawable name it: numberbkg.xml
Drawable XML (numberbkg)
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#DCAA0D" />
<corners android:radius="7dp" />
</shape>
Create a new resource file in Drawable name it: EdittextBkg.xml
Drawable XML (EdittextBkg)
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#071A28" />
<corners android:radius="7dp" />
</shape>