I'm basically making a simple calculator app using Kotlin. I'm very new to programming so I'm not familiar with Java either.
Basically the app runs and I'm also attaching a screenshot of the App's layout along with the code on MainActivity. Everything works fine except the clear button. Ideally, I want the clear button to reset the value on the 1st widget(results widget) and let me start a new calculation. Like how a AC button works on a regular calculator. However, all it does is clear the value. It doesn't clear the calculation. When I select the next calculation it still adds/subtracts/multiplies/divides to the previous value that's already there in the results widget. It doesn't let me start a new calculation like how I would be able to do if I pressed AC on a regular calculator.
Hope what i'm saying makes sense. Please tell me how can I make this work. And again, I'm very new to programming so would really really appreciate it if someone can help me.
package academy.learnprogramming.calculator
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
private const val TAG = "MainActivity"
private const val STATE_PENDING_OPERATION = "PendingOperation"
private const val STATE_OPERAND1 = "Operand1"
private const val STATE_OPERAND1_STORED = "Operand1_Stored"
class MainActivity : AppCompatActivity() {
private var operand1: Double? = null
private var operand2: Double = 0.0
private var pendingOperation = "="
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val listener = View.OnClickListener { v ->
val b = v as Button
newNumber.append(b.text)
}
button0.setOnClickListener(listener)
button1.setOnClickListener(listener)
button2.setOnClickListener(listener)
button3.setOnClickListener(listener)
button4.setOnClickListener(listener)
button5.setOnClickListener(listener)
button6.setOnClickListener(listener)
button7.setOnClickListener(listener)
button8.setOnClickListener(listener)
button9.setOnClickListener(listener)
buttonDot.setOnClickListener(listener)
val opListener = View.OnClickListener { v ->
val op = (v as Button).text.toString()
try {
val value = newNumber.text.toString().toDouble()
performOperation(value, op)
} catch (e: NumberFormatException) {
newNumber.setText("")
}
pendingOperation = op
operation.text = pendingOperation
}
buttonEquals.setOnClickListener(opListener)
buttonDivide.setOnClickListener(opListener)
buttonMultiply.setOnClickListener(opListener)
buttonMinus.setOnClickListener(opListener)
buttonPlus.setOnClickListener(opListener)
buttonNegative.setOnClickListener { view ->
val value = newNumber.text.toString()
if (value.isEmpty()) {
newNumber.setText("-")
} else {
try {
var doubleValue = value.toDouble()
doubleValue *= -1
newNumber.setText(doubleValue.toString())
} catch (e: NumberFormatException) {
newNumber.setText("")
}
}
}
val value = newNumber.text.toString()
buttonClear.setOnClickListener { view ->
val value = 0
if (value == 0){
result.setText("")
}
}
}
private fun performOperation(value: Double, operation: String) {
if (operand1 == null) {
operand1 = value
} else {
operand2 = value
if (pendingOperation == "=") {
pendingOperation = operation
}
when (pendingOperation) {
"=" -> operand1 =
operand2
"/" -> if (operand2 == 0.0) {
operand1 = Double.NaN
} else {
operand1 =
operand1!! / operand2
}
"*" -> operand1 = operand1!! * operand2
"-" -> operand1 = operand1!! - operand2
"+" -> operand1 = operand1!! + operand2
}
}
result.setText(operand1.toString())
newNumber.setText("")
}
override fun onSaveInstanceState(outState: Bundle) {
Log.d(TAG, "onSaveInstanceState: Called")
super.onSaveInstanceState(outState)
if (operand1 != null) {
outState.putDouble(
STATE_OPERAND1,
operand1!!
)
outState.putBoolean(
STATE_OPERAND1_STORED,
true
)
}
outState.putString(STATE_PENDING_OPERATION, pendingOperation)
}
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
if (savedInstanceState.getBoolean(
STATE_OPERAND1_STORED,
false
)
) {
operand1 = savedInstanceState.getDouble(STATE_OPERAND1)
} else {
operand1 = null
}
pendingOperation = savedInstanceState.getString(STATE_PENDING_OPERATION, "=")
operation.text = pendingOperation
}
}
Try resetting the values of operand1 and operand2
buttonClear.setOnClickListener { view ->
val value = 0
operand1 = null
operand2 = 0.0
if (value == 0){
result.setText("")
}
}
Related
I am learning Kotlin and can't seem to make sense of the different data types. I have a simple calculator app that currently adds or subtracts whole integers.
I am trying to make it possible to use "." to also calculate decimals. I've tried a number of things to convert the number to Double and don't think I am understanding the data types properly. I can get it to work explicitly with integers.
The closest I got to double was working but it automatically appended a zero after the decimal
package com.example.calctest
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.android.synthetic.main.activity_main.*
enum class CalculatorMode {
None,Add,Subtract
}
class MainActivity : AppCompatActivity() {
var lastButtonMode = false
var currentMode = CalculatorMode.None
var labelString = ""
var savedNum = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setupCalculator()
}
fun setupCalculator() {
val buttons = arrayOf(button_zero, button_1, button_2, button_3, button_4, button_5, button_6, button_7, button_8, button_9)
for(i in buttons.indices) {
buttons[i].setOnClickListener { calcNum(i) }
}
button_dot.setOnClickListener { calcDecimal() }
button_addition.setOnClickListener { changeMode(CalculatorMode.Add) }
button_subtraction.setOnClickListener { changeMode(CalculatorMode.Subtract) }
button_equals.setOnClickListener { calcEqual() }
button_clear.setOnClickListener { calcClear() }
}
fun calcEqual() {
if (lastButtonMode) {
return
}
val labelInt = labelString.toInt()
when(currentMode) {
CalculatorMode.Add -> savedNum += labelInt
CalculatorMode.Subtract -> savedNum -= labelInt
CalculatorMode.None -> return
}
currentMode = CalculatorMode.None
labelString = "$savedNum"
updateNumString()
lastButtonMode = true
}
fun calcClear() {
lastButtonMode = false
currentMode = CalculatorMode.None
labelString = ""
savedNum = 0
textView.text = "0"
}
fun updateNumString() {
val labelInt = labelString.toInt()
labelString = labelInt.toString()
if(currentMode == CalculatorMode.None) {
savedNum = labelInt
}
textView.text = labelString
}
fun changeMode(mode: CalculatorMode) {
if(savedNum == 0) {
return
}
currentMode = mode
lastButtonMode = true
}
fun calcNum(num: Int) {
val strVal = num.toString()
if (lastButtonMode) {
lastButtonMode = false
labelString = "0"
}
labelString = "$labelString$strVal"
updateNumString()
}
fun calcDecimal() {
if(labelString.contains(".")) {
return
} else {
labelString = "$labelString."
}
val labelDouble = labelString.toDouble()
labelString = labelDouble.toString()
textView.text = labelString
}
}
I want to load just one element with one scroll gesture. Now it is like one scroll gesture loads 1 or few new elements (depends on time of scroll gesture). As a solution I could do this gesture in shorter time than 500ms or make this postDelayed's delay longer but I guess there are better solutions for that. Do you have any ideas how to do that?
This app is written in MVP pattern. Here is my code:
CurrencyFragmentList.kt
private fun addScrollerListener() {
rvItem.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(_rvItem: RecyclerView, newState: Int) {
super.onScrollStateChanged(_rvItem, newState)
Log.e("scroll", isLoading.toString())
if (!isLoading) {
if (!_rvItem.canScrollVertically(1)) {
loadMore()
isLoading = true
}
}
}
})
}
private fun loadMore() {
showProgressDialog()
var numberOfDays = mainPresenter.getNumberOfMinusDays()
numberOfDays++
mainPresenter.saveNumberOfMinusDaysIntoSp(numberOfDays)
var dateMinusXDays = mainPresenter.currentDateMinusXDaysToStr(numberOfDays)
val nextLimit = listSize + 1
for (i in listSize until nextLimit) {
if (mainPresenter.checkIfSuchDateExistsinSp(dateMinusXDays)) {
Log.d("such date already exists in shared prefs", dateMinusXDays)
handler.postDelayed({
mainPresenter.processDateWithoutMakingACall(dateMinusXDays)
}, 500)
} else {
mainPresenter.makeACall(dateMinusXDays)
Log.d("retrofit call made", dateMinusXDays)
}
}
itemAdapter.notifyDataSetChanged()
}
override fun hideProgressDialog() {
if (apiResponseList.size > 1) {
apiResponseList.removeAt(apiResponseList.size - 1)
listSize = apiResponseList.size
itemAdapter.notifyItemRemoved(listSize)
} else progress_bar.visibility = View.GONE
isLoading = false
}
override fun assignResponseToRecyclerview(apiResponse: ApiResponse?) {
rvItem.apply {
layoutManager = _layoutManager
apiResponseList.add(apiResponse!!)
itemAdapter = activity?.let { ItemAdapter(apiResponseList, it) }!!
adapter = itemAdapter
}
Log.e("assign", isLoading.toString())
}
MainPresenter.kt
override fun makeACall(date: String?) {
//view.showProgressDialog()
date?.let { restModel.fetchApiResponse(this, it) }
}
fun processDateWithoutMakingACall(date: String) {
val apiResponse = processRawJson(sp.getString(date, "").toString())
passResponseToView(apiResponse)
}
override fun processRawJson(rawJson: String): ApiResponse {
val parser = JsonParser()
val rootObj = parser.parse(rawJson).asJsonObject
var ratesObj = JsonObject()
var ratesKeys: Set<String> = HashSet()
val ratesArrayList: ArrayList<Currency> = ArrayList()
val rootKeys = rootObj.keySet();
var baseValue = ""
var dateValue = ""
for (key in rootKeys) {
if (key == "base")
baseValue = rootObj.get(key).asString
if (key == "date")
dateValue = rootObj.get(key).asString
if (key == "rates") {
ratesObj = rootObj.get(key).asJsonObject
ratesKeys = ratesObj.keySet()
}
}
for (key2 in ratesKeys) {
Log.e("ratesKey", key2)
Log.e("ratesValue", ratesObj.get(key2).asFloat.toString())
ratesArrayList.add(Currency(key2, ratesObj.get(key2).asFloat))
}
saveRawJsonIntoSp(rawJson, dateValue)
return ApiResponse(baseValue, dateValue, ratesArrayList, false)
}
override fun passResponseToView(apiResponse: ApiResponse?) {
view.hideProgressDialog()
view.assignResponseToRecyclerview(apiResponse)
}
RestModel.kt
override fun fetchApiResponse(presenter: MainPresenter, date: String) {
job = CoroutineScope(Dispatchers.IO).launch {
val response = userService.getCurrenciesForDate(date)
withContext(Dispatchers.Main) {
if (response.isSuccessful) {
val rawJson = response.body()
val apiResponse = presenter.processRawJson(rawJson)
presenter.passResponseToView(apiResponse)
}
}
}
}
Any help will be really appreciated. Thank you in advance!
Try out the SnapHelper, it might slow layout manager to make more callbacks and stop overloading
Trying to show two Toasts with some info, but one of them isn't showing up.
The points of interest in the code below are functions showScore() and checkAnswer(). The first one doesn't show up its Toast, the second one does. The project builds successfully and the app is working on my phone (Android 9, aarch64).
I just started learning Android. Maybe it's something stupid simple, but I can't get the reason why it's not working.
class MainActivity : AppCompatActivity() {
private lateinit var trueButton: Button
private lateinit var falseButton: Button
private lateinit var nextButton: ImageButton
private lateinit var prevButton: ImageButton
private lateinit var questionTextView: TextView
private val questionBank = listOf(
Question(R.string.question_australia, true),
Question(R.string.question_oceans, true),
Question(R.string.question_mideast, false),
Question(R.string.question_africa, false),
Question(R.string.question_americas, true),
Question(R.string.question_asia, true)
)
private val answers = mutableMapOf<Question, Boolean>()
private var currentIndex = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
trueButton = findViewById(R.id.true_button)
falseButton = findViewById(R.id.false_button)
nextButton = findViewById(R.id.next_button)
prevButton = findViewById(R.id.prev_button)
questionTextView = findViewById(R.id.question_text_view)
trueButton.setOnClickListener { view: View ->
checkAnswer(true)
}
falseButton.setOnClickListener { view: View ->
checkAnswer(false)
}
nextButton.setOnClickListener {
currentIndex = (currentIndex + 1) % questionBank.size
updateQuestion()
checkIfAnswered()
}
prevButton.setOnClickListener {
currentIndex = if (currentIndex > 0) (currentIndex - 1) else (questionBank.size - 1)
updateQuestion()
checkIfAnswered()
}
questionTextView.setOnClickListener {
currentIndex = (currentIndex + 1) % questionBank.size
updateQuestion()
checkIfAnswered()
}
updateQuestion()
}
private fun updateQuestion() {
val questionTextResId = questionBank[currentIndex].textResId
questionTextView.setText(questionTextResId)
}
private fun checkAnswer(userAnswer: Boolean) {
val question = questionBank[currentIndex]
val correctAnswer = question.answer
if (!answers.containsKey(question)) {
answers[question] = userAnswer
falseButton.isEnabled = false
trueButton.isEnabled = false
if (answers.size == questionBank.size) {
showScore()
}
} else {
return
}
val messageResId = if (userAnswer == correctAnswer) {
R.string.correct_toast
} else {
R.string.incorrect_toast
}
Toast.makeText(this, messageResId, Toast.LENGTH_SHORT).show()
}
private fun checkIfAnswered() {
if (answers.containsKey(questionBank[currentIndex])) {
falseButton.isEnabled = false
trueButton.isEnabled = false
} else {
falseButton.isEnabled = true
trueButton.isEnabled = true
}
}
private fun showScore() {
var score = 0
answers.forEach {
if (it.key.answer == it.value) {
score += 1
}
}
val toastText = "You answered $score of ${questionBank.size}"
Toast.makeText(this, toastText, Toast.LENGTH_SHORT).show()
}
}
UDP: Tried to run on my friend's phone (Android 9, aarch64) and faced the same issue.
Thanks to one Android-related chat, I found the problem. The problem is that one Toast appears on top of another one. Generally, it's a bad idea to use Toasts this way. If you have a similar problem, consider using Snackbar.
I have this piece of code which is checking if it is close to the start time and if no repeats until it is started
I want to have less logic in ViewModel how can I refactor this piece of code?
uiScope.launch {
when (val unixTimeMillisecond = serverTimeFetcher.fetchUnixTime()) {
is Response.Success<Long> -> {
var title = "Play"
val startTimeMillis = FULL_DATE_FORMAT.parse(startTime).time
if (startTimeMillis > unixTimeMillisecond.data) {
val difference = startTimeMillis - unixTimeMillisecond.data
title = DURATION_FORMAT.format(Duration.millis(difference))
liveEventActionTitleMutableData.postValue(title)
delay(UPDATE_INTERVAL)
checkLiveEventStatus(startTime)
} else {
liveEventActionTitleMutableData.postValue(title)
}
}
is Response.Failure -> errorMessageMutableData.postValue(unixTimeMillisecond.message)
}
}
}
override suspend fun fetchUnixTime(): Response<Long> {
var timeDifference = apiPreferences.getLong("PREF_TIME_DIFFERENCE", -1)
val currentTimeMillis = System.currentTimeMillis()
return if (timeDifference == -1L) {
when (val serverTimeResult = fetchServerTime()) {
is Response.Success<ServerTime> -> {
val epochMillis = serverTimeResult.data.epochMillis
timeDifference = currentTimeMillis - epochMillis
apiPreferences.edit().putLong("PREF_TIME_DIFFERENCE", timeDifference).apply()
(Response.Success((epochMillis)))
}
else -> Response.Failure("error message")
}
} else {
val epochMillis = currentTimeMillis - timeDifference
Response.Success(epochMillis)
}
}
I'm using WorkManager 1.0.0-alpha02
I've created my worker class like:
class PrintWorker: Worker(){
override fun doWork(): WorkerResult {
try {
val label: String = inputData.getString(LABEL_ARG_CONTENIDO, null)
val isLastLabel: Boolean = inputData.getBoolean(LABEL_ARG_IS_LAST,false)
var result = Printer(applicationContext).print(label)
var outPut: Data = Data.Builder().putString("PrinterResponse",result.printerResponse).putBoolean(LABEL_ARG_IS_LAST,isLastLabel).build()
outputData = outPut
return result.workResult
}catch (e: Exception){
return WorkerResult.FAILURE
}
}
companion object {
const val LABEL_ARG_CONTENIDO = "Label"
const val LABEL_ARG_IS_LAST = "Position"
}
}
and then in my viewmodel I've schedulled the work chain like:
var myQueue: WorkContinuation? = null
for (label in labelEntities){
val newPrintWork = OneTimeWorkRequest.Builder(PrintWorker::class.java)
val builder = Data.Builder()
var data: Data
builder.putString(PrintWorker.LABEL_ARG_CONTENIDO, label.contenido)
if(myQueue == null){
data = builder.build()
newPrintWork.setInputData(data)
myQueue = WorkManager.getInstance().beginUniqueWork(printWorkId,ExistingWorkPolicy.REPLACE,newPrintWork.build())
}
else{
if(labelEntities.indexOf(label) == labelEntities.size - 1)
builder.putBoolean(PrintWorker.LABEL_ARG_IS_LAST, true)
data = builder.build()
newPrintWork.setInputData(data)
myQueue.then(newPrintWork.build())
}
}
myQueue?.enqueue()
finally in another piece of code I'm observing it with:
viewmodel.printStatus.observe(this, Observer { works ->
if(works!= null){
if(works.filter { it.state == State.FAILED}.isNotEmpty()){
MyWorkManager().cancelPrinting()
context?.let { showDialog(MyAlertDialogManager(it).noMacAddressErrorDialog()) }
}
if(works.filter { it.state == State.SUCCEEDED }.size == works.size &&
works.isNotEmpty() &&
works.filter { it.outputData.getBoolean(LABEL_ARG_IS_LAST,false) }.isNotEmpty()){
context?.let { showDialog(MyAlertDialogManager(it).confirmPrintedCorrectly(factura,this)) }
}
}
})
The first work gets done right after enqueueing and returns Worker.WorkerResult.SUCCESS but the rest of the chain doesnt gets called