mirror of
https://github.com/hmalik144/EasyCC_Master.git
synced 2025-12-10 03:05:29 +00:00
Issue resolved (#9)
* Issue resolved Took 2 hours 5 minutes * Issue resolved with unit tests Took 1 hour 31 minutes
This commit is contained in:
@@ -1,12 +1,7 @@
|
|||||||
package com.appttude.h_mal.easycc.data.network.api
|
package com.appttude.h_mal.easycc.data.network.api
|
||||||
|
|
||||||
import com.appttude.h_mal.easycc.data.network.interceptors.NetworkConnectionInterceptor
|
|
||||||
import com.appttude.h_mal.easycc.data.network.response.CurrencyResponse
|
import com.appttude.h_mal.easycc.data.network.response.CurrencyResponse
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import retrofit2.Retrofit
|
|
||||||
import retrofit2.converter.gson.GsonConverterFactory
|
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,14 @@
|
|||||||
package com.appttude.h_mal.easycc.data.network.api
|
package com.appttude.h_mal.easycc.data.network.api
|
||||||
|
|
||||||
import com.appttude.h_mal.easycc.data.network.interceptors.NetworkConnectionInterceptor
|
|
||||||
import com.appttude.h_mal.easycc.data.network.interceptors.QueryInterceptor
|
|
||||||
import com.appttude.h_mal.easycc.data.network.response.ResponseObject
|
import com.appttude.h_mal.easycc.data.network.response.ResponseObject
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
|
||||||
import retrofit2.Response
|
import retrofit2.Response
|
||||||
import retrofit2.Retrofit
|
|
||||||
import retrofit2.converter.gson.GsonConverterFactory
|
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrofit Network class to currency api calls
|
* Retrofit Network class to currency api calls
|
||||||
*/
|
*/
|
||||||
interface CurrencyApi : Api{
|
interface CurrencyApi : Api {
|
||||||
|
|
||||||
// Get rate from server with arguments passed in Repository
|
// Get rate from server with arguments passed in Repository
|
||||||
@GET("convert?")
|
@GET("convert?")
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import retrofit2.Retrofit
|
|||||||
import retrofit2.converter.gson.GsonConverterFactory
|
import retrofit2.converter.gson.GsonConverterFactory
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RemoteDataSource @Inject constructor(){
|
class RemoteDataSource @Inject constructor() {
|
||||||
|
|
||||||
fun <Api> buildApi(
|
fun <Api> buildApi(
|
||||||
okkHttpclient: OkHttpClient,
|
okkHttpclient: OkHttpClient,
|
||||||
|
|||||||
@@ -5,22 +5,22 @@ import com.appttude.h_mal.easycc.models.CurrencyModelInterface
|
|||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
data class CurrencyResponse(
|
data class CurrencyResponse(
|
||||||
@field:SerializedName("date")
|
@field:SerializedName("date")
|
||||||
val date: String? = null,
|
val date: String? = null,
|
||||||
@field:SerializedName("amount")
|
@field:SerializedName("amount")
|
||||||
val amount: Double? = null,
|
val amount: Double? = null,
|
||||||
@field:SerializedName("rates")
|
@field:SerializedName("rates")
|
||||||
var rates: Map<String, Double>? = null,
|
var rates: Map<String, Double>? = null,
|
||||||
@field:SerializedName("base")
|
@field:SerializedName("base")
|
||||||
val base: String? = null
|
val base: String? = null
|
||||||
) : CurrencyModelInterface {
|
) : CurrencyModelInterface {
|
||||||
|
|
||||||
override fun getCurrencyModel(): CurrencyModel {
|
override fun getCurrencyModel(): CurrencyModel {
|
||||||
return CurrencyModel(
|
return CurrencyModel(
|
||||||
base,
|
base,
|
||||||
rates?.iterator()?.next()?.key,
|
rates?.iterator()?.next()?.key,
|
||||||
rates?.iterator()?.next()?.value ?: 0.0
|
rates?.iterator()?.next()?.value ?: 0.0
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,13 @@
|
|||||||
package com.appttude.h_mal.easycc.data.repository
|
package com.appttude.h_mal.easycc.data.repository
|
||||||
|
|
||||||
import com.appttude.h_mal.easycc.data.network.response.CurrencyResponse
|
import com.appttude.h_mal.easycc.models.CurrencyModel
|
||||||
import com.appttude.h_mal.easycc.data.network.response.ResponseObject
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Main entry point for accessing currency data.
|
* Main entry point for accessing currency data.
|
||||||
*/
|
*/
|
||||||
interface Repository {
|
interface Repository {
|
||||||
|
|
||||||
suspend fun getDataFromApi(fromCurrency: String, toCurrency: String): ResponseObject
|
suspend fun getDataFromApi(fromCurrency: String, toCurrency: String): CurrencyModel
|
||||||
|
|
||||||
suspend fun getBackupDataFromApi(fromCurrency: String, toCurrency: String): CurrencyResponse
|
|
||||||
|
|
||||||
fun getConversionPair(): Pair<String?, String?>
|
fun getConversionPair(): Pair<String?, String?>
|
||||||
|
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import com.appttude.h_mal.easycc.R
|
|||||||
import com.appttude.h_mal.easycc.data.network.SafeApiRequest
|
import com.appttude.h_mal.easycc.data.network.SafeApiRequest
|
||||||
import com.appttude.h_mal.easycc.data.network.api.BackupCurrencyApi
|
import com.appttude.h_mal.easycc.data.network.api.BackupCurrencyApi
|
||||||
import com.appttude.h_mal.easycc.data.network.api.CurrencyApi
|
import com.appttude.h_mal.easycc.data.network.api.CurrencyApi
|
||||||
import com.appttude.h_mal.easycc.data.network.response.CurrencyResponse
|
|
||||||
import com.appttude.h_mal.easycc.data.network.response.ResponseObject
|
|
||||||
import com.appttude.h_mal.easycc.data.prefs.PreferenceProvider
|
import com.appttude.h_mal.easycc.data.prefs.PreferenceProvider
|
||||||
|
import com.appttude.h_mal.easycc.models.CurrencyModel
|
||||||
import com.appttude.h_mal.easycc.utils.convertPairsListToString
|
import com.appttude.h_mal.easycc.utils.convertPairsListToString
|
||||||
|
import java.io.IOException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,17 +23,19 @@ class RepositoryImpl @Inject constructor(
|
|||||||
override suspend fun getDataFromApi(
|
override suspend fun getDataFromApi(
|
||||||
fromCurrency: String,
|
fromCurrency: String,
|
||||||
toCurrency: String
|
toCurrency: String
|
||||||
): ResponseObject {
|
): CurrencyModel {
|
||||||
// Set currency pairs as correct string for api query eg. AUD_GBP
|
return try {
|
||||||
val currencyPair = convertPairsListToString(fromCurrency, toCurrency)
|
// Set currency pairs as correct string for api query eg. AUD_GBP
|
||||||
return responseUnwrap { api.getCurrencyRate(currencyPair) }
|
val currencyPair = convertPairsListToString(fromCurrency, toCurrency)
|
||||||
}
|
responseUnwrap { api.getCurrencyRate(currencyPair) }.getCurrencyModel()
|
||||||
|
} catch (e: IOException) {
|
||||||
override suspend fun getBackupDataFromApi(
|
responseUnwrap {
|
||||||
fromCurrency: String,
|
backUpApi.getCurrencyRate(
|
||||||
toCurrency: String
|
fromCurrency,
|
||||||
): CurrencyResponse {
|
toCurrency
|
||||||
return responseUnwrap { backUpApi.getCurrencyRate(fromCurrency, toCurrency) }
|
)
|
||||||
|
}.getCurrencyModel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getConversionPair(): Pair<String?, String?> {
|
override fun getConversionPair(): Pair<String?, String?> {
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
package com.appttude.h_mal.easycc.helper
|
|
||||||
|
|
||||||
import com.appttude.h_mal.easycc.data.repository.Repository
|
|
||||||
import com.appttude.h_mal.easycc.models.CurrencyModelInterface
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class CurrencyDataHelper @Inject constructor(
|
|
||||||
val repository: Repository
|
|
||||||
) {
|
|
||||||
|
|
||||||
suspend fun getDataFromApi(from: String, to: String): CurrencyModelInterface {
|
|
||||||
return try {
|
|
||||||
repository.getDataFromApi(from, to)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
repository.getBackupDataFromApi(from, to)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,6 @@ import com.appttude.h_mal.easycc.utils.trimToThree
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class WidgetHelper @Inject constructor(
|
class WidgetHelper @Inject constructor(
|
||||||
private val helper: CurrencyDataHelper,
|
|
||||||
val repository: Repository
|
val repository: Repository
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@@ -16,7 +15,7 @@ class WidgetHelper @Inject constructor(
|
|||||||
val s1 = pair.first?.trimToThree() ?: return null
|
val s1 = pair.first?.trimToThree() ?: return null
|
||||||
val s2 = pair.second?.trimToThree() ?: return null
|
val s2 = pair.second?.trimToThree() ?: return null
|
||||||
|
|
||||||
return helper.getDataFromApi(s1, s2).getCurrencyModel()
|
return repository.getDataFromApi(s1, s2)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -3,12 +3,12 @@ package com.appttude.h_mal.easycc.models
|
|||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
data class CurrencyObject(
|
data class CurrencyObject(
|
||||||
@SerializedName("id")
|
@SerializedName("id")
|
||||||
var id: String,
|
var id: String,
|
||||||
@SerializedName("fr")
|
@SerializedName("fr")
|
||||||
var fr: String,
|
var fr: String,
|
||||||
@SerializedName("to")
|
@SerializedName("to")
|
||||||
var to: String,
|
var to: String,
|
||||||
@SerializedName("val")
|
@SerializedName("val")
|
||||||
var value: Double
|
var value: Double
|
||||||
)
|
)
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package com.appttude.h_mal.easycc.ui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup.LayoutParams
|
||||||
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.appttude.h_mal.easycc.R
|
||||||
|
import com.appttude.h_mal.easycc.utils.*
|
||||||
|
|
||||||
|
abstract class BaseActivity<V : BaseViewModel> : AppCompatActivity() {
|
||||||
|
|
||||||
|
private lateinit var loadingView: View
|
||||||
|
|
||||||
|
abstract val viewModel: V?
|
||||||
|
|
||||||
|
private var loading: Boolean = false
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
configureObserver()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a loading view which to be shown during async operations
|
||||||
|
*
|
||||||
|
* #setOnClickListener(null) is an ugly work around to prevent under being clicked during
|
||||||
|
* loading
|
||||||
|
*/
|
||||||
|
private fun instantiateLoadingView() {
|
||||||
|
loadingView = layoutInflater.inflate(R.layout.progress_layout, null)
|
||||||
|
loadingView.setOnClickListener(null)
|
||||||
|
addContentView(loadingView, LayoutParams(MATCH_PARENT, MATCH_PARENT))
|
||||||
|
|
||||||
|
loadingView.hide()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
instantiateLoadingView()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <A : AppCompatActivity> startActivity(activity: Class<A>) {
|
||||||
|
val intent = Intent(this, activity)
|
||||||
|
startActivity(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun onStarted() {
|
||||||
|
loadingView.fadeIn()
|
||||||
|
loading = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called in case of success or some data emitted from the liveData in viewModel
|
||||||
|
*/
|
||||||
|
open fun onSuccess(data: Any?) {
|
||||||
|
loadingView.fadeOut()
|
||||||
|
loading = false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called in case of failure or some error emitted from the liveData in viewModel
|
||||||
|
*/
|
||||||
|
open fun onFailure(error: String?) {
|
||||||
|
error?.let { displayToast(it) }
|
||||||
|
loadingView.fadeOut()
|
||||||
|
loading = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun configureObserver() {
|
||||||
|
viewModel?.uiState?.observe(this) {
|
||||||
|
when (it) {
|
||||||
|
is ViewState.HasStarted -> onStarted()
|
||||||
|
is ViewState.HasData<*> -> onSuccess(it.data.getContentIfNotHandled())
|
||||||
|
is ViewState.HasError -> onFailure(it.error.getContentIfNotHandled())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun View.fadeIn() = apply {
|
||||||
|
show()
|
||||||
|
triggerAnimation(android.R.anim.fade_in) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun View.fadeOut() = apply {
|
||||||
|
hide()
|
||||||
|
triggerAnimation(android.R.anim.fade_out) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onBackPressed() {
|
||||||
|
if (!loading) super.onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.appttude.h_mal.easycc.ui
|
||||||
|
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.appttude.h_mal.easycc.utils.Event
|
||||||
|
import com.appttude.h_mal.easycc.utils.ViewState
|
||||||
|
|
||||||
|
abstract class BaseViewModel : ViewModel() {
|
||||||
|
open val uiState: MutableLiveData<ViewState> = MutableLiveData()
|
||||||
|
|
||||||
|
fun onStart() {
|
||||||
|
uiState.postValue(ViewState.HasStarted)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun <T : Any> onSuccess(result: T) {
|
||||||
|
uiState.postValue(ViewState.HasData(Event(result)))
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun onError(error: String) {
|
||||||
|
uiState.postValue(ViewState.HasError(Event(error)))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,6 @@ import android.view.WindowManager
|
|||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import android.widget.ListView
|
import android.widget.ListView
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.appcompat.widget.SearchView
|
|
||||||
import com.appttude.h_mal.easycc.R
|
import com.appttude.h_mal.easycc.R
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -18,7 +17,7 @@ import com.appttude.h_mal.easycc.R
|
|||||||
@Suppress("DEPRECATION")
|
@Suppress("DEPRECATION")
|
||||||
class CustomDialogClass(
|
class CustomDialogClass(
|
||||||
context: Context,
|
context: Context,
|
||||||
private val clickListener: ClickListener
|
val onSelect: (String) -> Unit
|
||||||
) : Dialog(context) {
|
) : Dialog(context) {
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -30,7 +29,6 @@ class CustomDialogClass(
|
|||||||
// Keyboard not to overlap dialog
|
// Keyboard not to overlap dialog
|
||||||
window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
|
||||||
|
|
||||||
// array adapter for list of currencies in R.Strings
|
|
||||||
val arrayAdapter =
|
val arrayAdapter =
|
||||||
ArrayAdapter.createFromResource(
|
ArrayAdapter.createFromResource(
|
||||||
context, R.array.currency_arrays,
|
context, R.array.currency_arrays,
|
||||||
@@ -52,13 +50,8 @@ class CustomDialogClass(
|
|||||||
|
|
||||||
// interface selection back to calling activity
|
// interface selection back to calling activity
|
||||||
list_view.setOnItemClickListener { adapterView, _, i, _ ->
|
list_view.setOnItemClickListener { adapterView, _, i, _ ->
|
||||||
clickListener.onText(adapterView.getItemAtPosition(i).toString())
|
onSelect.invoke(adapterView.getItemAtPosition(i).toString())
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interface to handle selection within dialog
|
|
||||||
interface ClickListener {
|
|
||||||
fun onText(currencyName: String)
|
|
||||||
}
|
|
||||||
@@ -7,18 +7,16 @@ import android.view.View
|
|||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import com.appttude.h_mal.easycc.R
|
|
||||||
import com.appttude.h_mal.easycc.databinding.ActivityMainBinding
|
import com.appttude.h_mal.easycc.databinding.ActivityMainBinding
|
||||||
|
import com.appttude.h_mal.easycc.models.CurrencyModel
|
||||||
|
import com.appttude.h_mal.easycc.ui.BaseActivity
|
||||||
import com.appttude.h_mal.easycc.utils.clearEditText
|
import com.appttude.h_mal.easycc.utils.clearEditText
|
||||||
import com.appttude.h_mal.easycc.utils.displayToast
|
|
||||||
import com.appttude.h_mal.easycc.utils.hideView
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MainActivity : AppCompatActivity(), View.OnClickListener {
|
class MainActivity : BaseActivity<MainViewModel>(), View.OnClickListener {
|
||||||
|
|
||||||
private val viewModel: MainViewModel by viewModels()
|
override val viewModel: MainViewModel by viewModels()
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -29,34 +27,22 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
|||||||
* Prevent keyboard overlapping views
|
* Prevent keyboard overlapping views
|
||||||
*/
|
*/
|
||||||
window.setSoftInputMode(
|
window.setSoftInputMode(
|
||||||
WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN or
|
WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN
|
||||||
WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
|
|
||||||
)
|
)
|
||||||
|
|
||||||
viewModel.initiate(intent.extras)
|
viewModel.initiate(intent.extras)
|
||||||
|
|
||||||
binding.currencyOne.text = viewModel.rateIdTo
|
binding.currencyOne.text = viewModel.rateIdFrom
|
||||||
binding.currencyTwo.text = viewModel.rateIdFrom
|
binding.currencyTwo.text = viewModel.rateIdTo
|
||||||
|
|
||||||
setUpListeners()
|
setUpListeners()
|
||||||
setUpObservers()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setUpObservers() {
|
override fun onSuccess(data: Any?) {
|
||||||
viewModel.operationStartedListener.observe(this) {
|
super.onSuccess(data)
|
||||||
binding.progressBar.hideView(false)
|
if (data is CurrencyModel) {
|
||||||
}
|
binding.bottomInsertValues.clearEditText()
|
||||||
viewModel.operationFinishedListener.observe(this) { pair ->
|
binding.topInsertValue.clearEditText()
|
||||||
// hide progress bar
|
|
||||||
binding.progressBar.hideView(true)
|
|
||||||
if (pair.first) {
|
|
||||||
// Operation was successful remove text in EditTexts
|
|
||||||
binding.bottomInsertValues.clearEditText()
|
|
||||||
binding.topInsertValue.clearEditText()
|
|
||||||
} else {
|
|
||||||
// Display Toast with error message returned from Viewmodel
|
|
||||||
pair.second?.let { displayToast(it) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,25 +55,21 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun showCustomDialog(view: View?) {
|
private fun showCustomDialog(view: View?) {
|
||||||
CustomDialogClass(this, object : ClickListener {
|
CustomDialogClass(this) {
|
||||||
override fun onText(currencyName: String) {
|
(view as TextView).text = it
|
||||||
(view as TextView).text = currencyName
|
viewModel.setCurrencyName(view.tag, it)
|
||||||
viewModel.setCurrencyName(view.tag, currencyName)
|
}.show()
|
||||||
}
|
|
||||||
|
|
||||||
}).show()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(view: View?) {
|
override fun onClick(view: View?) {
|
||||||
showCustomDialog(view)
|
showCustomDialog(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text watcher applied to EditText @topInsertValue
|
|
||||||
private val textWatcherClass: TextWatcher = object : TextWatcher {
|
private val textWatcherClass: TextWatcher = object : TextWatcher {
|
||||||
override fun onTextChanged(s: CharSequence, st: Int, b: Int, c: Int) {
|
override fun onTextChanged(s: CharSequence, st: Int, b: Int, c: Int) {
|
||||||
// Remove text watcher on other text watcher to prevent infinite loop
|
// Remove text watcher on other text watcher to prevent infinite loop
|
||||||
binding.bottomInsertValues.removeTextChangedListener(textWatcherClass2)
|
binding.bottomInsertValues.removeTextChangedListener(textWatcherClass2)
|
||||||
// Clear any values if current EditText is empty
|
|
||||||
if (binding.topInsertValue.text.isNullOrEmpty())
|
if (binding.topInsertValue.text.isNullOrEmpty())
|
||||||
binding.bottomInsertValues.setText("")
|
binding.bottomInsertValues.setText("")
|
||||||
}
|
}
|
||||||
@@ -102,7 +84,6 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
|||||||
|
|
||||||
private val textWatcherClass2: TextWatcher = object : TextWatcher {
|
private val textWatcherClass2: TextWatcher = object : TextWatcher {
|
||||||
override fun onTextChanged(s: CharSequence, st: Int, b: Int, c: Int) {
|
override fun onTextChanged(s: CharSequence, st: Int, b: Int, c: Int) {
|
||||||
|
|
||||||
binding.topInsertValue.removeTextChangedListener(textWatcherClass)
|
binding.topInsertValue.removeTextChangedListener(textWatcherClass)
|
||||||
if (binding.bottomInsertValues.text.isNullOrEmpty())
|
if (binding.bottomInsertValues.text.isNullOrEmpty())
|
||||||
binding.topInsertValue.clearEditText()
|
binding.topInsertValue.clearEditText()
|
||||||
@@ -115,4 +96,5 @@ class MainActivity : AppCompatActivity(), View.OnClickListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
package com.appttude.h_mal.easycc.ui.main
|
package com.appttude.h_mal.easycc.ui.main
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import androidx.lifecycle.SavedStateHandle
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.appttude.h_mal.easycc.data.repository.Repository
|
import com.appttude.h_mal.easycc.data.repository.Repository
|
||||||
import com.appttude.h_mal.easycc.helper.CurrencyDataHelper
|
import com.appttude.h_mal.easycc.ui.BaseViewModel
|
||||||
import com.appttude.h_mal.easycc.utils.toTwoDpString
|
import com.appttude.h_mal.easycc.utils.toTwoDpString
|
||||||
import com.appttude.h_mal.easycc.utils.trimToThree
|
import com.appttude.h_mal.easycc.utils.trimToThree
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -21,60 +16,48 @@ import javax.inject.Inject
|
|||||||
*/
|
*/
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class MainViewModel @Inject constructor(
|
class MainViewModel @Inject constructor(
|
||||||
private val currencyDataHelper: CurrencyDataHelper,
|
|
||||||
private val repository: Repository
|
private val repository: Repository
|
||||||
) : ViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
private val conversionPairs by lazy { repository.getConversionPair() }
|
private val conversionPairs by lazy { repository.getConversionPair() }
|
||||||
|
|
||||||
// Viewbinding to textviews in @activity_main.xml
|
// Viewbinded variables
|
||||||
var rateIdFrom: String? = null
|
var rateIdFrom: String? = null
|
||||||
var rateIdTo: String? = null
|
var rateIdTo: String? = null
|
||||||
|
|
||||||
//operation results livedata based on outcome of operation
|
|
||||||
val operationStartedListener = MutableLiveData<Boolean>()
|
|
||||||
val operationFinishedListener = MutableLiveData<Pair<Boolean, String?>>()
|
|
||||||
|
|
||||||
private var conversionRate: Double = 1.00
|
private var conversionRate: Double = 1.00
|
||||||
|
|
||||||
private fun getExchangeRate() {
|
private fun getExchangeRate() {
|
||||||
operationStartedListener.postValue(true)
|
onStart()
|
||||||
|
|
||||||
// view binded exchange rates selected null checked
|
|
||||||
if (rateIdFrom.isNullOrEmpty() || rateIdTo.isNullOrEmpty()) {
|
if (rateIdFrom.isNullOrEmpty() || rateIdTo.isNullOrEmpty()) {
|
||||||
operationFinishedListener.postValue(Pair(false, "Select currencies"))
|
onError("Select both currencies")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// No need to call api as it will return exchange rate as 1
|
|
||||||
if (rateIdFrom == rateIdTo) {
|
if (rateIdFrom == rateIdTo) {
|
||||||
conversionRate = 1.00
|
conversionRate = 1.00
|
||||||
operationFinishedListener.postValue(Pair(true, null))
|
onError("Currency selections are the same")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
// Non-null assertion (!!) as values have been null checked and have not changed
|
// Non-null assertion (!!) as values have been null checked and have not changed
|
||||||
val exchangeResponse = currencyDataHelper.getDataFromApi(
|
val exchangeResponse = repository.getDataFromApi(
|
||||||
rateIdFrom!!.trimToThree(),
|
rateIdFrom!!.trimToThree(),
|
||||||
rateIdTo!!.trimToThree()
|
rateIdTo!!.trimToThree()
|
||||||
)
|
)
|
||||||
|
|
||||||
exchangeResponse.getCurrencyModel().let {
|
exchangeResponse.let {
|
||||||
conversionRate = it.rate
|
conversionRate = it.rate
|
||||||
repository.setConversionPair(rateIdFrom!!, rateIdTo!!)
|
repository.setConversionPair(rateIdFrom!!, rateIdTo!!)
|
||||||
|
|
||||||
operationFinishedListener.postValue(Pair(true, null))
|
onSuccess(it)
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
e.message?.let {
|
e.message?.let { onError(it) }
|
||||||
operationFinishedListener.postValue(Pair(false, it))
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
operationFinishedListener.postValue(Pair(false, "Failed to retrieve rate"))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,9 @@ import android.os.Bundle
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import com.appttude.h_mal.easycc.databinding.CurrencyAppWidgetConfigureBinding
|
import com.appttude.h_mal.easycc.databinding.CurrencyAppWidgetConfigureBinding
|
||||||
import com.appttude.h_mal.easycc.ui.main.ClickListener
|
import com.appttude.h_mal.easycc.ui.BaseActivity
|
||||||
import com.appttude.h_mal.easycc.ui.main.CustomDialogClass
|
import com.appttude.h_mal.easycc.ui.main.CustomDialogClass
|
||||||
import com.appttude.h_mal.easycc.utils.displayToast
|
|
||||||
import com.appttude.h_mal.easycc.utils.transformIntToArray
|
import com.appttude.h_mal.easycc.utils.transformIntToArray
|
||||||
import com.appttude.h_mal.easycc.widget.CurrencyAppWidgetKotlin
|
import com.appttude.h_mal.easycc.widget.CurrencyAppWidgetKotlin
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
@@ -20,16 +18,16 @@ import dagger.hilt.android.AndroidEntryPoint
|
|||||||
* The configuration screen for the [CurrencyAppWidgetKotlin] AppWidget.
|
* The configuration screen for the [CurrencyAppWidgetKotlin] AppWidget.
|
||||||
*/
|
*/
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class CurrencyAppWidgetConfigureActivityKotlin : AppCompatActivity(),
|
class CurrencyAppWidgetConfigureActivityKotlin : BaseActivity<WidgetViewModel>(),
|
||||||
View.OnClickListener {
|
View.OnClickListener {
|
||||||
|
|
||||||
val viewModel: WidgetViewModel by viewModels()
|
override val viewModel: WidgetViewModel by viewModels()
|
||||||
|
|
||||||
private lateinit var binding: CurrencyAppWidgetConfigureBinding
|
private lateinit var binding: CurrencyAppWidgetConfigureBinding
|
||||||
private var mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID
|
private var mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID
|
||||||
|
|
||||||
public override fun onCreate(icicle: Bundle?) {
|
public override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(icicle)
|
super.onCreate(savedInstanceState)
|
||||||
binding = CurrencyAppWidgetConfigureBinding.inflate(layoutInflater)
|
binding = CurrencyAppWidgetConfigureBinding.inflate(layoutInflater)
|
||||||
setContentView(binding.root)
|
setContentView(binding.root)
|
||||||
|
|
||||||
@@ -50,10 +48,8 @@ class CurrencyAppWidgetConfigureActivityKotlin : AppCompatActivity(),
|
|||||||
finish()
|
finish()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.initiate(mAppWidgetId)
|
viewModel.initiate(mAppWidgetId)
|
||||||
|
|
||||||
setupObserver()
|
|
||||||
setupClickListener()
|
setupClickListener()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,17 +59,9 @@ class CurrencyAppWidgetConfigureActivityKotlin : AppCompatActivity(),
|
|||||||
binding.currencyTwo.setOnClickListener(this)
|
binding.currencyTwo.setOnClickListener(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupObserver() {
|
override fun onSuccess(data: Any?) {
|
||||||
viewModel.operationFinishedListener.observe(this) {
|
super.onSuccess(data)
|
||||||
|
displaySubmitDialog()
|
||||||
// it.first is a the success of the operation
|
|
||||||
if (it.first) {
|
|
||||||
displaySubmitDialog()
|
|
||||||
} else {
|
|
||||||
// failed operation - display toast with message from it.second
|
|
||||||
it.second?.let { message -> displayToast(message) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(view: View?) {
|
override fun onClick(view: View?) {
|
||||||
@@ -98,12 +86,10 @@ class CurrencyAppWidgetConfigureActivityKotlin : AppCompatActivity(),
|
|||||||
|
|
||||||
|
|
||||||
private fun showCustomDialog(view: View?) {
|
private fun showCustomDialog(view: View?) {
|
||||||
CustomDialogClass(this, object : ClickListener {
|
CustomDialogClass(this) {
|
||||||
override fun onText(currencyName: String) {
|
(view as TextView).text = it
|
||||||
(view as TextView).text = currencyName
|
viewModel.setCurrencyName(view.tag, it)
|
||||||
viewModel.setCurrencyName(view.tag, currencyName)
|
}.show()
|
||||||
}
|
|
||||||
}).show()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun finishCurrencyWidgetActivity() {
|
fun finishCurrencyWidgetActivity() {
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ package com.appttude.h_mal.easycc.ui.widget
|
|||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import com.appttude.h_mal.easycc.R
|
|
||||||
import com.appttude.h_mal.easycc.databinding.ActivityMainBinding
|
|
||||||
import com.appttude.h_mal.easycc.databinding.ConfirmDialogBinding
|
import com.appttude.h_mal.easycc.databinding.ConfirmDialogBinding
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
package com.appttude.h_mal.easycc.ui.widget
|
package com.appttude.h_mal.easycc.ui.widget
|
||||||
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import com.appttude.h_mal.easycc.data.repository.Repository
|
import com.appttude.h_mal.easycc.data.repository.Repository
|
||||||
|
import com.appttude.h_mal.easycc.ui.BaseViewModel
|
||||||
import com.appttude.h_mal.easycc.utils.trimToThree
|
import com.appttude.h_mal.easycc.utils.trimToThree
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -10,7 +9,7 @@ import javax.inject.Inject
|
|||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class WidgetViewModel @Inject constructor(
|
class WidgetViewModel @Inject constructor(
|
||||||
private val repository: Repository
|
private val repository: Repository
|
||||||
) : ViewModel() {
|
) : BaseViewModel() {
|
||||||
|
|
||||||
private val defaultCurrency: String by lazy { repository.getCurrenciesList()[0] }
|
private val defaultCurrency: String by lazy { repository.getCurrenciesList()[0] }
|
||||||
var appWidgetId: Int? = null
|
var appWidgetId: Int? = null
|
||||||
@@ -20,7 +19,6 @@ class WidgetViewModel @Inject constructor(
|
|||||||
var rateIdTo: String? = null
|
var rateIdTo: String? = null
|
||||||
|
|
||||||
// Live data to feedback to @CurrencyAppWidgetConfigureActivityKotlin
|
// Live data to feedback to @CurrencyAppWidgetConfigureActivityKotlin
|
||||||
val operationFinishedListener = MutableLiveData<Pair<Boolean, String?>>()
|
|
||||||
|
|
||||||
// Setup viewmodel app widget ID
|
// Setup viewmodel app widget ID
|
||||||
// Set default values for text views
|
// Set default values for text views
|
||||||
@@ -43,15 +41,14 @@ class WidgetViewModel @Inject constructor(
|
|||||||
|
|
||||||
fun submitSelectionOnClick() {
|
fun submitSelectionOnClick() {
|
||||||
if (rateIdTo == null || rateIdFrom == null) {
|
if (rateIdTo == null || rateIdFrom == null) {
|
||||||
operationFinishedListener.value = Pair(false, "Selections incomplete")
|
onError("Selections incomplete")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (rateIdFrom == rateIdTo) {
|
if (rateIdFrom == rateIdTo) {
|
||||||
operationFinishedListener.value =
|
onError("Selected rates cannot be the same")
|
||||||
Pair(false, "Selected rates cannot be the same ${rateIdFrom}${rateIdTo}")
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
operationFinishedListener.value = Pair(true, null)
|
onSuccess(Unit)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setWidgetStored() {
|
fun setWidgetStored() {
|
||||||
|
|||||||
24
app/src/main/java/com/appttude/h_mal/easycc/utils/Event.kt
Normal file
24
app/src/main/java/com/appttude/h_mal/easycc/utils/Event.kt
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package com.appttude.h_mal.easycc.utils
|
||||||
|
|
||||||
|
open class Event<out T>(private val content: T) {
|
||||||
|
|
||||||
|
var hasBeenHandled = false
|
||||||
|
private set // Allow external read but not write
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the content and prevents its use again.
|
||||||
|
*/
|
||||||
|
fun getContentIfNotHandled(): T? {
|
||||||
|
return if (hasBeenHandled) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
hasBeenHandled = true
|
||||||
|
content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the content, even if it's already been handled.
|
||||||
|
*/
|
||||||
|
fun peekContent(): T = content
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package com.appttude.h_mal.easycc.utils
|
||||||
|
|
||||||
|
sealed class ViewState {
|
||||||
|
object HasStarted : ViewState()
|
||||||
|
class HasData<T : Any>(val data: Event<T>) : ViewState()
|
||||||
|
class HasError(val error: Event<String>) : ViewState()
|
||||||
|
}
|
||||||
@@ -2,21 +2,34 @@ package com.appttude.h_mal.easycc.utils
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.view.animation.Animation
|
||||||
|
import android.view.animation.AnimationUtils
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.annotation.AnimRes
|
||||||
|
|
||||||
fun EditText.clearEditText() {
|
fun EditText.clearEditText() {
|
||||||
this.setText("")
|
this.setText("")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun View.hideView(vis: Boolean) {
|
|
||||||
visibility = if (vis) {
|
|
||||||
View.GONE
|
|
||||||
} else {
|
|
||||||
View.VISIBLE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.displayToast(message: String) {
|
fun Context.displayToast(message: String) {
|
||||||
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
|
Toast.makeText(this, message, Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun View.triggerAnimation(@AnimRes id: Int, complete: (View) -> Unit) {
|
||||||
|
val animation = AnimationUtils.loadAnimation(context, id)
|
||||||
|
animation.setAnimationListener(object : Animation.AnimationListener {
|
||||||
|
override fun onAnimationEnd(animation: Animation?) = complete(this@triggerAnimation)
|
||||||
|
override fun onAnimationStart(a: Animation?) {}
|
||||||
|
override fun onAnimationRepeat(a: Animation?) {}
|
||||||
|
})
|
||||||
|
startAnimation(animation)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun View.show() {
|
||||||
|
this.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
fun View.hide() {
|
||||||
|
this.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:aapt="http://schemas.android.com/aapt"
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
android:width="108dp"
|
android:width="1080dp"
|
||||||
android:height="192dp"
|
android:height="1920dp"
|
||||||
android:viewportWidth="1080"
|
android:viewportWidth="1080"
|
||||||
android:viewportHeight="1920">
|
android:viewportHeight="1920">
|
||||||
<path android:pathData="M0,0L1080,0L1080,1920L0,1920L0,0Z">
|
<path android:pathData="M0,0L1080,0L1080,1920L0,1920L0,0Z">
|
||||||
|
|||||||
@@ -99,17 +99,4 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<ProgressBar
|
|
||||||
android:id="@+id/progressBar"
|
|
||||||
style="?android:attr/progressBarStyle"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:indeterminate="true"
|
|
||||||
android:indeterminateTint="@color/colour_four"
|
|
||||||
android:indeterminateTintMode="src_atop"
|
|
||||||
android:visibility="gone"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|||||||
18
app/src/main/res/layout/progress_layout.xml
Normal file
18
app/src/main/res/layout/progress_layout.xml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="#4D000000">
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
style="?android:attr/progressBarStyle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:indeterminate="true"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:elevation="0.2dp"
|
||||||
|
android:indeterminateTint="@color/colour_four"
|
||||||
|
android:indeterminateTintMode="src_atop"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
package com.appttude.h_mal.easycc.repository
|
package com.appttude.h_mal.easycc.repository
|
||||||
|
|
||||||
|
import com.appttude.h_mal.easycc.data.network.SafeApiRequest
|
||||||
import com.appttude.h_mal.easycc.data.network.api.BackupCurrencyApi
|
import com.appttude.h_mal.easycc.data.network.api.BackupCurrencyApi
|
||||||
import com.appttude.h_mal.easycc.data.network.api.CurrencyApi
|
import com.appttude.h_mal.easycc.data.network.api.CurrencyApi
|
||||||
import com.appttude.h_mal.easycc.data.network.response.ResponseObject
|
import com.appttude.h_mal.easycc.data.network.response.ResponseObject
|
||||||
import com.appttude.h_mal.easycc.data.prefs.PreferenceProvider
|
import com.appttude.h_mal.easycc.data.prefs.PreferenceProvider
|
||||||
import com.appttude.h_mal.easycc.data.repository.Repository
|
import com.appttude.h_mal.easycc.data.repository.Repository
|
||||||
import com.appttude.h_mal.easycc.data.repository.RepositoryImpl
|
import com.appttude.h_mal.easycc.data.repository.RepositoryImpl
|
||||||
|
import com.appttude.h_mal.easycc.models.CurrencyModel
|
||||||
import com.appttude.h_mal.easycc.utils.convertPairsListToString
|
import com.appttude.h_mal.easycc.utils.convertPairsListToString
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import okhttp3.ResponseBody
|
import okhttp3.ResponseBody
|
||||||
@@ -22,7 +24,7 @@ import java.io.IOException
|
|||||||
import kotlin.test.assertFailsWith
|
import kotlin.test.assertFailsWith
|
||||||
|
|
||||||
|
|
||||||
class RepositoryNetworkTest {
|
class RepositoryNetworkTest : SafeApiRequest() {
|
||||||
|
|
||||||
lateinit var repository: Repository
|
lateinit var repository: Repository
|
||||||
|
|
||||||
@@ -49,15 +51,16 @@ class RepositoryNetworkTest {
|
|||||||
//create a successful retrofit response
|
//create a successful retrofit response
|
||||||
val mockCurrencyResponse = mock(ResponseObject::class.java)
|
val mockCurrencyResponse = mock(ResponseObject::class.java)
|
||||||
val re = Response.success(mockCurrencyResponse)
|
val re = Response.success(mockCurrencyResponse)
|
||||||
|
val currencyModel = mock(CurrencyModel::class.java)
|
||||||
|
|
||||||
//WHEN - loginApiRequest to return a successful response
|
//WHEN - loginApiRequest to return a successful response
|
||||||
val currencyPair = convertPairsListToString(s1, s2)
|
val currencyPair = convertPairsListToString(s1, s2)
|
||||||
Mockito.`when`(api.getCurrencyRate(currencyPair)).thenReturn(re)
|
Mockito.`when`(api.getCurrencyRate(currencyPair)).thenReturn(re)
|
||||||
|
Mockito.`when`(responseUnwrap { api.getCurrencyRate(currencyPair) }.getCurrencyModel()).thenReturn(currencyModel)
|
||||||
|
|
||||||
//THEN - the unwrapped login response contains the correct values
|
//THEN - the unwrapped login response contains the correct values
|
||||||
val currencyResponse = repository.getDataFromApi(s1, s2)
|
val currencyResponse = repository.getDataFromApi(s1, s2)
|
||||||
assertNotNull(currencyResponse)
|
assertEquals(currencyResponse, currencyModel)
|
||||||
assertEquals(currencyResponse, mockCurrencyResponse)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -74,6 +77,7 @@ class RepositoryNetworkTest {
|
|||||||
//WHEN
|
//WHEN
|
||||||
val currencyPair = convertPairsListToString(s1, s2)
|
val currencyPair = convertPairsListToString(s1, s2)
|
||||||
Mockito.`when`(api.getCurrencyRate(currencyPair)).thenAnswer { re }
|
Mockito.`when`(api.getCurrencyRate(currencyPair)).thenAnswer { re }
|
||||||
|
Mockito.`when`(apiBackup.getCurrencyRate(s1, s2)).thenAnswer { re }
|
||||||
|
|
||||||
//THEN - assert exception is not null
|
//THEN - assert exception is not null
|
||||||
val ioExceptionReturned = assertFailsWith<IOException> {
|
val ioExceptionReturned = assertFailsWith<IOException> {
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package com.appttude.h_mal.easycc.ui
|
||||||
|
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import com.appttude.h_mal.easycc.utils.ViewState
|
||||||
|
|
||||||
|
abstract class BaseViewModelTest<V : BaseViewModel> {
|
||||||
|
|
||||||
|
abstract val viewModel: V?
|
||||||
|
|
||||||
|
open fun setUp() {
|
||||||
|
viewModel?.uiState?.observeForever {
|
||||||
|
when (it) {
|
||||||
|
is ViewState.HasStarted -> Unit
|
||||||
|
is ViewState.HasData<*> -> dataPost.postValue(it.data.getContentIfNotHandled())
|
||||||
|
is ViewState.HasError -> errorPost.postValue(it.error.getContentIfNotHandled())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataPost: MutableLiveData<Any> = MutableLiveData()
|
||||||
|
var errorPost: MutableLiveData<String> = MutableLiveData()
|
||||||
|
}
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
package com.appttude.h_mal.easycc.ui.main
|
package com.appttude.h_mal.easycc.ui.main
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
|
||||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||||
import com.appttude.h_mal.easycc.data.network.response.ResponseObject
|
|
||||||
import com.appttude.h_mal.easycc.data.repository.Repository
|
import com.appttude.h_mal.easycc.data.repository.Repository
|
||||||
import com.appttude.h_mal.easycc.helper.CurrencyDataHelper
|
import com.appttude.h_mal.easycc.models.CurrencyModel
|
||||||
|
import com.appttude.h_mal.easycc.ui.BaseViewModelTest
|
||||||
import com.appttude.h_mal.easycc.utils.MainCoroutineRule
|
import com.appttude.h_mal.easycc.utils.MainCoroutineRule
|
||||||
import com.appttude.h_mal.easycc.utils.observeOnce
|
import com.appttude.h_mal.easycc.utils.observeOnce
|
||||||
import com.nhaarman.mockitokotlin2.doAnswer
|
import com.nhaarman.mockitokotlin2.doAnswer
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.junit.Assert.*
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertNull
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -22,7 +22,7 @@ import org.mockito.MockitoAnnotations
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
class MainViewModelTest {
|
class MainViewModelTest : BaseViewModelTest<MainViewModel>(){
|
||||||
|
|
||||||
// Run tasks synchronously
|
// Run tasks synchronously
|
||||||
@get:Rule
|
@get:Rule
|
||||||
@@ -31,27 +31,27 @@ class MainViewModelTest {
|
|||||||
@get:Rule
|
@get:Rule
|
||||||
var mainCoroutineRule = MainCoroutineRule()
|
var mainCoroutineRule = MainCoroutineRule()
|
||||||
|
|
||||||
lateinit var viewModel: MainViewModel
|
override lateinit var viewModel: MainViewModel
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
lateinit var repository: Repository
|
lateinit var repository: Repository
|
||||||
|
|
||||||
@Mock
|
private val currencyOne = "AUD - Australian Dollar"
|
||||||
lateinit var helper: CurrencyDataHelper
|
private val currencyTwo = "GBP - British Pound"
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
override fun setUp() {
|
||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
viewModel = MainViewModel(helper, repository)
|
viewModel = MainViewModel(repository)
|
||||||
|
|
||||||
|
super.setUp()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun initiate_validBundleValues_successResponse() = runBlocking {
|
fun initiate_validBundleValues_successResponse() = runBlocking {
|
||||||
//GIVEN
|
//GIVEN
|
||||||
val currencyOne = "AUD - Australian Dollar"
|
|
||||||
val currencyTwo = "GBP - British Pound"
|
|
||||||
val bundle = mock(Bundle()::class.java)
|
val bundle = mock(Bundle()::class.java)
|
||||||
val responseObject = mock(ResponseObject::class.java)
|
val responseObject = mock(CurrencyModel::class.java)
|
||||||
|
|
||||||
//WHEN
|
//WHEN
|
||||||
Mockito.`when`(bundle.getString("parse_1")).thenReturn(currencyOne)
|
Mockito.`when`(bundle.getString("parse_1")).thenReturn(currencyOne)
|
||||||
@@ -61,59 +61,50 @@ class MainViewModelTest {
|
|||||||
|
|
||||||
//THEN
|
//THEN
|
||||||
viewModel.initiate(bundle)
|
viewModel.initiate(bundle)
|
||||||
viewModel.operationStartedListener.observeOnce {
|
|
||||||
assertEquals(true, it)
|
dataPost.observeOnce {
|
||||||
}
|
assertEquals(it, responseObject)
|
||||||
viewModel.operationFinishedListener.observeOnce {
|
|
||||||
assertEquals(true, it.first)
|
|
||||||
assertNull(it.second)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun initiate_invalidBundleValues_successfulResponse() = runBlocking {
|
fun initiate_invalidBundleValues_successfulResponse() = runBlocking {
|
||||||
//GIVEN
|
//GIVEN
|
||||||
val currencyOne = "corrupted data"
|
|
||||||
val currencyTwo = "corrupted data again"
|
|
||||||
val bundle = mock(Bundle()::class.java)
|
val bundle = mock(Bundle()::class.java)
|
||||||
val error = "Corrupted data found"
|
val error = "Corrupted data found"
|
||||||
|
|
||||||
//WHEN
|
//WHEN
|
||||||
Mockito.`when`(bundle.getString("parse_1")).thenReturn(currencyOne)
|
Mockito.`when`(bundle.getString("parse_1")).thenReturn(currencyOne)
|
||||||
Mockito.`when`(bundle.getString("parse_2")).thenReturn(currencyTwo)
|
Mockito.`when`(bundle.getString("parse_2")).thenReturn(currencyTwo)
|
||||||
Mockito.`when`(helper.getDataFromApi(currencyOne, currencyTwo))
|
Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo))
|
||||||
.doAnswer { throw IOException(error) }
|
.doAnswer { throw IOException(error) }
|
||||||
|
|
||||||
//THEN
|
//THEN
|
||||||
viewModel.initiate(bundle)
|
viewModel.initiate(bundle)
|
||||||
viewModel.operationStartedListener.observeOnce {
|
|
||||||
assertEquals(true, it)
|
errorPost.observeOnce {
|
||||||
}
|
assertEquals(error, it)
|
||||||
viewModel.operationFinishedListener.observeOnce {
|
|
||||||
assertEquals(false, it.first)
|
|
||||||
assertEquals(it.second, error)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun initiate_sameBundleValues_successfulResponse() = runBlocking {
|
fun initiate_sameBundleValues_successfulResponse() = runBlocking {
|
||||||
//GIVEN
|
//GIVEN
|
||||||
val currencyOne = "AUD - Australian Dollar"
|
|
||||||
val bundle = mock(Bundle()::class.java)
|
val bundle = mock(Bundle()::class.java)
|
||||||
|
val responseObject = mock(CurrencyModel::class.java)
|
||||||
|
|
||||||
//WHEN
|
//WHEN
|
||||||
Mockito.`when`(bundle.getString("parse_1")).thenReturn(null)
|
Mockito.`when`(bundle.getString("parse_1")).thenReturn(null)
|
||||||
Mockito.`when`(bundle.getString("parse_2")).thenReturn(null)
|
Mockito.`when`(bundle.getString("parse_2")).thenReturn(null)
|
||||||
Mockito.`when`(repository.getConversionPair()).thenReturn(Pair(currencyOne, currencyOne))
|
Mockito.`when`(repository.getConversionPair()).thenReturn(Pair(currencyOne, currencyOne))
|
||||||
|
Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo))
|
||||||
|
.thenReturn(responseObject)
|
||||||
|
|
||||||
//THEN
|
//THEN
|
||||||
viewModel.initiate(bundle)
|
viewModel.initiate(bundle)
|
||||||
viewModel.operationStartedListener.observeOnce {
|
|
||||||
assertEquals(true, it)
|
dataPost.observeOnce {
|
||||||
}
|
assertEquals(responseObject, it)
|
||||||
viewModel.operationFinishedListener.observeOnce {
|
|
||||||
assertEquals(true, it.first)
|
|
||||||
assertNull(it.second)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,12 +120,9 @@ class MainViewModelTest {
|
|||||||
|
|
||||||
//THEN
|
//THEN
|
||||||
viewModel.initiate(bundle)
|
viewModel.initiate(bundle)
|
||||||
viewModel.operationStartedListener.observeOnce {
|
|
||||||
assertEquals(true, it)
|
errorPost.observeOnce {
|
||||||
}
|
assertEquals("Select both currencies", it)
|
||||||
viewModel.operationFinishedListener.observeOnce {
|
|
||||||
assertEquals(false, it.first)
|
|
||||||
assertEquals("Select currencies", it.second)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,11 +130,9 @@ class MainViewModelTest {
|
|||||||
@Test
|
@Test
|
||||||
fun setCurrencyName_validValues_successResponse() = runBlocking {
|
fun setCurrencyName_validValues_successResponse() = runBlocking {
|
||||||
//GIVEN
|
//GIVEN
|
||||||
val currencyOne = "AUD - Australian Dollar"
|
|
||||||
val currencyTwo = "GBP - British Pound"
|
|
||||||
viewModel.rateIdTo = currencyTwo
|
viewModel.rateIdTo = currencyTwo
|
||||||
val tag = "top"
|
val tag = "top"
|
||||||
val responseObject = mock(ResponseObject::class.java)
|
val responseObject = mock(CurrencyModel::class.java)
|
||||||
|
|
||||||
//WHEN
|
//WHEN
|
||||||
Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo))
|
Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo))
|
||||||
@@ -154,24 +140,18 @@ class MainViewModelTest {
|
|||||||
|
|
||||||
//THEN
|
//THEN
|
||||||
viewModel.setCurrencyName(tag, currencyOne)
|
viewModel.setCurrencyName(tag, currencyOne)
|
||||||
viewModel.operationStartedListener.observeOnce {
|
|
||||||
assertEquals(true, it)
|
dataPost.observeOnce {
|
||||||
}
|
assertEquals(responseObject, it)
|
||||||
viewModel.operationFinishedListener.observeOnce {
|
|
||||||
assertEquals(true, it.first)
|
|
||||||
Log.i("tag", "${it.first} ${it.second}")
|
|
||||||
assertNull(it.second)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun setCurrencyName_sameValues_successfulResponse() = runBlocking {
|
fun setCurrencyName_sameValues_successfulResponse() = runBlocking {
|
||||||
//GIVEN
|
//GIVEN
|
||||||
val currencyOne = "AUD - Australian Dollar"
|
|
||||||
val currencyTwo = "GBP - British Pound"
|
|
||||||
viewModel.rateIdTo = currencyOne
|
viewModel.rateIdTo = currencyOne
|
||||||
val tag = "top"
|
val tag = "top"
|
||||||
val responseObject = mock(ResponseObject::class.java)
|
val responseObject = mock(CurrencyModel::class.java)
|
||||||
|
|
||||||
//WHEN
|
//WHEN
|
||||||
Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo))
|
Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo))
|
||||||
@@ -179,35 +159,27 @@ class MainViewModelTest {
|
|||||||
|
|
||||||
//THEN
|
//THEN
|
||||||
viewModel.setCurrencyName(tag, currencyOne)
|
viewModel.setCurrencyName(tag, currencyOne)
|
||||||
viewModel.operationStartedListener.observeOnce {
|
dataPost.observeOnce {
|
||||||
assertEquals(true, it)
|
assertEquals(responseObject, it)
|
||||||
}
|
|
||||||
viewModel.operationFinishedListener.observeOnce {
|
|
||||||
assertEquals(true, it.first)
|
|
||||||
assertNull(it.second)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun setCurrencyName_invalidValues_unsuccessfulResponse() = runBlocking {
|
fun setCurrencyName_invalidValues_unsuccessfulResponse() = runBlocking {
|
||||||
//GIVEN
|
//GIVEN
|
||||||
val currencyOne = "AUD - Australian Dollar"
|
val error = "Data is corrupted"
|
||||||
val currencyTwo = "GBP - British Pound"
|
viewModel.rateIdTo = "corrupted"
|
||||||
val tag = "top"
|
val tag = "top"
|
||||||
val responseObject = mock(ResponseObject::class.java)
|
val responseObject = mock(CurrencyModel::class.java)
|
||||||
|
|
||||||
//WHEN
|
//WHEN
|
||||||
Mockito.`when`(repository.getDataFromApi(currencyOne, currencyTwo))
|
Mockito.`when`(repository.getDataFromApi(currencyOne, "corrupted"))
|
||||||
.thenReturn(responseObject)
|
.doAnswer { throw IOException(error) }
|
||||||
|
|
||||||
//THEN
|
//THEN
|
||||||
viewModel.setCurrencyName(tag, currencyOne)
|
viewModel.setCurrencyName(tag, currencyOne)
|
||||||
viewModel.operationStartedListener.observeOnce {
|
errorPost.observeOnce {
|
||||||
assertEquals(true, it)
|
assertEquals(error, it)
|
||||||
}
|
|
||||||
viewModel.operationFinishedListener.observeOnce {
|
|
||||||
assertEquals(false, it.first)
|
|
||||||
assertNotNull(it.second)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.appttude.h_mal.easycc.ui.widget
|
|||||||
|
|
||||||
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
|
||||||
import com.appttude.h_mal.easycc.data.repository.Repository
|
import com.appttude.h_mal.easycc.data.repository.Repository
|
||||||
|
import com.appttude.h_mal.easycc.ui.BaseViewModelTest
|
||||||
import com.appttude.h_mal.easycc.utils.observeOnce
|
import com.appttude.h_mal.easycc.utils.observeOnce
|
||||||
import org.junit.Assert.*
|
import org.junit.Assert.*
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
@@ -15,21 +16,23 @@ import org.mockito.MockitoAnnotations
|
|||||||
private const val currencyOne = "AUD - Australian Dollar"
|
private const val currencyOne = "AUD - Australian Dollar"
|
||||||
private const val currencyTwo = "GBP - British Pound"
|
private const val currencyTwo = "GBP - British Pound"
|
||||||
|
|
||||||
class WidgetViewModelTest {
|
class WidgetViewModelTest : BaseViewModelTest<WidgetViewModel>(){
|
||||||
|
|
||||||
// Run tasks synchronously
|
// Run tasks synchronously
|
||||||
@get:Rule
|
@get:Rule
|
||||||
val instantTaskExecutorRule: InstantTaskExecutorRule = InstantTaskExecutorRule()
|
val instantTaskExecutorRule: InstantTaskExecutorRule = InstantTaskExecutorRule()
|
||||||
|
|
||||||
lateinit var viewModel: WidgetViewModel
|
override lateinit var viewModel: WidgetViewModel
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
lateinit var repository: Repository
|
lateinit var repository: Repository
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
fun setUp() {
|
override fun setUp() {
|
||||||
MockitoAnnotations.initMocks(this)
|
MockitoAnnotations.initMocks(this)
|
||||||
viewModel = WidgetViewModel(repository)
|
viewModel = WidgetViewModel(repository)
|
||||||
|
|
||||||
|
super.setUp()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -75,7 +78,6 @@ class WidgetViewModelTest {
|
|||||||
//THEN
|
//THEN
|
||||||
val dialogResult = viewModel.getSubmitDialogMessage()
|
val dialogResult = viewModel.getSubmitDialogMessage()
|
||||||
assertEquals(dialogResult, "Create widget for AUDGBP?")
|
assertEquals(dialogResult, "Create widget for AUDGBP?")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -86,9 +88,9 @@ class WidgetViewModelTest {
|
|||||||
|
|
||||||
//THEN
|
//THEN
|
||||||
viewModel.submitSelectionOnClick()
|
viewModel.submitSelectionOnClick()
|
||||||
viewModel.operationFinishedListener.observeOnce {
|
|
||||||
assertEquals(it.first, true)
|
dataPost.observeOnce {
|
||||||
assertNull(it.second)
|
assert(it is Unit)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,20 +102,23 @@ class WidgetViewModelTest {
|
|||||||
|
|
||||||
//THEN
|
//THEN
|
||||||
viewModel.submitSelectionOnClick()
|
viewModel.submitSelectionOnClick()
|
||||||
viewModel.operationFinishedListener.observeOnce {
|
|
||||||
assertEquals(it.first, false)
|
errorPost.observeOnce {
|
||||||
assertNotNull(it.second)
|
assertEquals("Selected rates cannot be the same", it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun submitSelectionOnClick_noInput_unsuccessfulResponse() {
|
fun submitSelectionOnClick_noInput_unsuccessfulResponse() {
|
||||||
|
//GIVEN
|
||||||
|
viewModel.rateIdFrom = null
|
||||||
|
viewModel.rateIdTo = null
|
||||||
|
|
||||||
//THEN
|
//THEN
|
||||||
viewModel.submitSelectionOnClick()
|
viewModel.submitSelectionOnClick()
|
||||||
viewModel.operationFinishedListener.observeOnce {
|
|
||||||
assertEquals(it.first, false)
|
errorPost.observeOnce {
|
||||||
assertNotNull(it.second)
|
assertEquals("Selections incomplete", it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user