Answer To: Currency Conversion Application This application converts US dollars to euros, British pounds,...
Gaurav answered on Oct 28 2021
CurrencyConverter-master/.gitignore
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties
CurrencyConverter-master/app/.gitignore
/build
CurrencyConverter-master/app/build.gradle
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
defaultConfig {
applicationId "com.plcoding.currencyconverter"
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildFeatures {
viewBinding true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
//Dagger - Hilt
implementation "com.google.dagger:hilt-android:2.28-alpha"
kapt "com.google.dagger:hilt-android-compiler:2.28-alpha"
implementation "androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha02"
kapt "androidx.hilt:hilt-compiler:1.0.0-alpha02"
// Activity KTX for viewModels()
implementation "androidx.activity:activity-ktx:1.1.0"
// Architectural Components
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
// Lifecycle
implementation "androidx.lifecycle:lifecycle-extensions:2.2.0"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation "com.squareup.okhttp3:okhttp:4.9.0"
// Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'
// Coroutine Lifecycle Scopes
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0"
}
CurrencyConverter-master/app/proguard-rules.pro
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
CurrencyConverter-master/app/src/androidTest/java/com/plcoding/currencyconverter/ExampleInstrumentedTest.kt
package com.plcoding.currencyconverter
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.plcoding.currencyconverter", appContext.packageName)
}
}
CurrencyConverter-master/app/src/main/AndroidManifest.xml
CurrencyConverter-master/app/src/main/java/com/plcoding/currencyconverter/CurrencyApplication.kt
package com.plcoding.currencyconverter
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class CurrencyApplication : Application()
CurrencyConverter-master/app/src/main/java/com/plcoding/currencyconverter/data/CurrencyApi.kt
package com.plcoding.currencyconverter.data
import com.plcoding.currencyconverter.data.models.CurrencyResponse
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Query
interface CurrencyApi {
@GET("/latest?access_key=978a6115d94b1d918f59f66755804089")
suspend fun getRates(
@Query("base") base: String
): Response
}
CurrencyConverter-master/app/src/main/java/com/plcoding/currencyconverter/data/models/CurrencyResponse.kt
package com.plcoding.currencyconverter.data.models
import com.google.gson.annotations.SerializedName
data class CurrencyResponse(
val base: String,
val date: String,
val rates: Rates
)
CurrencyConverter-master/app/src/main/java/com/plcoding/currencyconverter/data/models/Rates.kt
package com.plcoding.currencyconverter.data.models
import com.google.gson.annotations.SerializedName
data class Rates(
@SerializedName("AUD")
val aUD: Double,
@SerializedName("BGN")
val bGN: Double,
@SerializedName("EUR")
val eUR: Double,
@SerializedName("BRL")
val bRL: Double,
@SerializedName("CAD")
val cAD: Double,
@SerializedName("CHF")
val cHF: Double,
@SerializedName("CNY")
val cNY: Double,
@SerializedName("CZK")
val cZK: Double,
@SerializedName("DKK")
val dKK: Double,
@SerializedName("GBP")
val gBP: Double,
@SerializedName("HKD")
val hKD: Double,
@SerializedName("HRK")
val hRK: Double,
@SerializedName("HUF")
val hUF: Double,
@SerializedName("IDR")
val iDR: Double,
@SerializedName("ILS")
val iLS: Double,
@SerializedName("INR")
val iNR: Double,
@SerializedName("ISK")
val iSK: Double,
@SerializedName("JPY")
val jPY: Double,
@SerializedName("KRW")
val kRW: Double,
@SerializedName("MXN")
val mXN: Double,
@SerializedName("MYR")
val mYR: Double,
@SerializedName("NOK")
val nOK: Double,
@SerializedName("NZD")
val nZD: Double,
@SerializedName("PHP")
val pHP: Double,
@SerializedName("PLN")
val pLN: Double,
@SerializedName("RON")
val rON: Double,
@SerializedName("RUB")
val rUB: Double,
@SerializedName("SEK")
val sEK: Double,
@SerializedName("SGD")
val sGD: Double,
@SerializedName("THB")
val tHB: Double,
@SerializedName("TRY")
val tRY: Double,
@SerializedName("USD")
val uSD: Double,
@SerializedName("ZAR")
val zAR: Double
)
CurrencyConverter-master/app/src/main/java/com/plcoding/currencyconverter/di/AppModule.kt
package com.plcoding.currencyconverter.di
import com.plcoding.currencyconverter.data.CurrencyApi
import com.plcoding.currencyconverter.main.DefaultMainRepository
import com.plcoding.currencyconverter.main.MainRepository
import com.plcoding.currencyconverter.util.DispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ApplicationComponent
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import javax.inject.Singleton
private const val BASE_URL = "http://api.exchangeratesapi.io/v1/"
@Module
@InstallIn(ApplicationComponent::class)
object AppModule {
@Singleton
@Provides
fun provideCurrencyApi(): CurrencyApi = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(CurrencyApi::class.java)
@Singleton
@Provides
fun provideMainRepository(api: CurrencyApi): MainRepository = DefaultMainRepository(api)
@Singleton
@Provides
fun provideDispatchers(): DispatcherProvider = object : DispatcherProvider {
override val main: CoroutineDispatcher
get() = Dispatchers.Main
override val io: CoroutineDispatcher
get() = Dispatchers.IO
override val default: CoroutineDispatcher
get() = Dispatchers.Default
override val unconfined: CoroutineDispatcher
get() = Dispatchers.Unconfined
}
}
CurrencyConverter-master/app/src/main/java/com/plcoding/currencyconverter/main/DefaultMainRepository.kt
package com.plcoding.currencyconverter.main
import com.plcoding.currencyconverter.data.CurrencyApi
import com.plcoding.currencyconverter.data.models.CurrencyResponse
import com.plcoding.currencyconverter.util.Resource
import javax.inject.Inject
class DefaultMainRepository @Inject constructor(
private val api: CurrencyApi
) : MainRepository {
override suspend fun getRates(base: String): Resource {
return try {
val response = api.getRates(base)
val result = response.body()
if(response.isSuccessful && result != null) {
Resource.Success(result)
} else {
Resource.Error(response.message())
}
} catch(e: Exception) {
Resource.Error(e.message ?: "An error occured")
}
}
}
CurrencyConverter-master/app/src/main/java/com/plcoding/currencyconverter/main/MainRepository.kt
package com.plcoding.currencyconverter.main
import com.plcoding.currencyconverter.data.models.CurrencyResponse
import com.plcoding.currencyconverter.util.Resource
interface MainRepository {
suspend fun getRates(base: String): Resource
}
CurrencyConverter-master/app/src/main/java/com/plcoding/currencyconverter/main/MainViewModel.kt
package com.plcoding.currencyconverter.main
import androidx.hilt.lifecycle.ViewModelInject
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.plcoding.currencyconverter.data.models.Rates
import com.plcoding.currencyconverter.util.DispatcherProvider
import com.plcoding.currencyconverter.util.Resource
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlin.math.round
class MainViewModel @ViewModelInject constructor(
private val repository: MainRepository,
private val dispatchers: DispatcherProvider
): ViewModel() {
sealed class CurrencyEvent {
class Success(val resultText: String): CurrencyEvent()
class Failure(val errorText: String): CurrencyEvent()
object Loading : CurrencyEvent()
object Empty : CurrencyEvent()
}
private val _conversion = MutableStateFlow(CurrencyEvent.Empty)
val conversion: StateFlow = _conversion
fun convert(
amountStr: String,
fromCurrency: String,
toCurrency: String
) {
val fromAmount = amountStr.toFloatOrNull()
if(fromAmount == null) {
_conversion.value = CurrencyEvent.Failure("Not a valid amount")
return
}
viewModelScope.launch(dispatchers.io) {
_conversion.value = CurrencyEvent.Loading
when(val ratesResponse = repository.getRates(fromCurrency)) {
is Resource.Error -> _conversion.value = CurrencyEvent.Failure(ratesResponse.message!!)
is Resource.Success -> {
val rates = ratesResponse.data!!.rates
val rate = getRateForCurrency(toCurrency, rates)
if(rate == null) {
_conversion.value = CurrencyEvent.Failure("Unexpected error")
// Log.d("Errorerer","This is the error")
} else {
val convertedCurrency = round(fromAmount * rate * 100) / 100
_conversion.value = CurrencyEvent.Success(
"$fromAmount $fromCurrency = $convertedCurrency $toCurrency"
)
}
}
}
}
}
private fun getRateForCurrency(currency: String, rates: Rates) = when (currency) {
"CAD" -> rates.cAD
"HKD" -> rates.hKD
"ISK" -> rates.iSK
"EUR" -> rates.eUR
"PHP" -> rates.pHP
"DKK" -> rates.dKK
"HUF" -> rates.hUF
"CZK" -> rates.cZK
"AUD" -> rates.aUD
"RON" -> rates.rON
"SEK" -> rates.sEK
"IDR" -> rates.iDR
"INR" -> rates.iNR
"BRL" -> rates.bRL
"RUB" -> rates.rUB
"HRK" -> rates.hRK
"JPY" -> rates.jPY
"THB" -> rates.tHB
"CHF" -> rates.cHF
"SGD" -> rates.sGD
"PLN" -> rates.pLN
"BGN" -> rates.bGN
"CNY" -> rates.cNY
"NOK" -> rates.nOK
"NZD" -> rates.nZD
"ZAR" -> rates.zAR
"USD" -> rates.uSD
"MXN" -> rates.mXN
"ILS" -> rates.iLS
"GBP" -> rates.gBP
"KRW" -> rates.kRW
"MYR" -> rates.mYR
else -> null
}
}
CurrencyConverter-master/app/src/main/java/com/plcoding/currencyconverter/MainActivity.kt
package com.plcoding.currencyconverter
import android.graphics.Color
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.viewModels
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.plcoding.currencyconverter.databinding.ActivityMainBinding
import com.plcoding.currencyconverter.main.MainViewModel
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collect
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.btnConvert.setOnClickListener {
viewModel.convert(
binding.etFrom.text.toString(),
binding.spFromCurrency.selectedItem.toString(),
binding.spToCurrency.selectedItem.toString(),
)
}
lifecycleScope.launchWhenStarted {
viewModel.conversion.collect { event ->
...