/SimpleCurrency

A simple application that converts currency

Primary LanguageKotlin

SimpleCurrency

Simple Currency is an application that handles currency conversion from 168 countries.

Demo Gif

Here's a quick demo of how it works:

click to show demo gif

Demo Screenshots

Here are some screenshots:

click to show screenshots
HomeScreen CurrencyScreen 1 CurrencyScreen 2 Landscape

Features

  • convert currency from one to another
  • support decimal point
  • long press on 'x' will clear all input
  • swap button to switch between the base and target currency quickly
  • automatic currency rate updates every 30 minutes
  • pick from 168 currencies
  • filter currency by currency name or code in currency picker
  • comma seperation for big numbers
  • landscape mode supported

How to Setup

The API used for obtaining the latest currency data is CurrencyLayer.com. A free account can be made easily and it will provide an API key. You need to fill up the API key in api_keys.properties file at the root of this project. Instructions:

  1. make a file named api_keys.properties in the root of the project
  2. add this line in the file: CURRENCY_LAYER_ACCESS_TOKEN=<fill in API key obtained from Currency Layer>

This is how the file looks like:

༼つ◕_◕༽つ RootOfSimpleCurrency (master)$ cat api_keys.properties
CURRENCY_LAYER_ACCESS_TOKEN=d0bbf06c7xxxxxxxxxxxx47d2e56ed6f

Tech Explanation

The following dependencies are used in this project:

  • RxJava
  • RxBinding
  • Dagger2
  • Retrofit
  • Moshi
  • Room
  • Android Arch Lifecycle
  • Android Arch ViewModel
  • WorkManager
  • Material Design Library
  • Mockk
  • JUnit
  • etc..

Programming Language

This project is written in Kotlin.

Architecture

MVVM is used in this app. The view layer is made reactive and passed into the ViewModel as the input. External sources that are needed (such as database access, network calls, shared preference access) will be passed into ViewModel as Repo. This way ViewModel doesn't have access to Android-related code so that it can be unit tested.

Room Persistence library is used to access SQLite easily. This is used to store the currency data. USD is used as the base currency, so all the currency stored in the database is referenced against USD. Let's say we wanted to find out Japanese Yen (JPY) vs. Pound Sterling (GBP), simple math calculation will be done.

In every periodic interval (currently set at every 30 minutes), the WorkManager will fire up a Retrofit call to get the latest currency rate. The obtained json will be deserialized by Moshi and write into the database.

The currency layer network library is extracted into a separate module so that it can be decoupled from the main app. It can be launched as a standalone separate network library or being swapped out and replaced by another currency API.

Unidirectional data flow & Immutable data

The project follows the unidirectional data flow rule to better structure the code.

Here's a brief diagram of the main activity architecture.

  1. RxView - The flow begins from the views. Every user view interactions are reactive and fed into the ViewModel.
  2. Repo - Any external data access that is not from the user interactions, such as network calls, shared preference, database access, etc... will all go through the Repo class that is passed into ViewModel.
  3. ScreenState - The reactive signals from Views will be processed inside ViewModel by some business logic. After that, it will produce the next ScreenState by using .copy() from Kotlin to ensure immutability.
  4. View Update - Finally, all the views will listen to the ScreenState and update itself accordingly.

This architecture is quite similar to MVI. The difference is that it doesn't use reducer or model the input as 'intent'.

Unit Test

ViewModel doesn't have access to Android related code. Therefore, it can be tested by JUnit test, without using androidTest. Example Unit Test can be found MainViewModelTest.kt.

The general idea of testing an Activity can be described by this diagram:

The RxViews signals are replaced by fake inputs from the test. Reactive RxBinding signals are be replaced by RxJava's Subjects , reactive database access Flowable are replaced by RxJava's Processors and normal method calls are mocked by mockk. This way, user interactions can be controlled by us. After that, we can make Assertion on the ScreenState to check if it behaves correctly.

Here's an example of testing a simple conversion (MainViewModelTest.kt#L112):

    fun testSimpleConversion() {
        // 1. arrange
        val fakeRate = 2.0
        viewModel.onCreate()
        val screenStateTestObserver = viewModel.screenState.test()

        // 2. act
        populateDbIfFirstTime.onNext(true)
        getLatestSelectedRateFlowable.offer(fakeRate)

        onNumpad1Click.onNext('1')
        onNumpad0Click.onNext('0')
        onNumpad0Click.onNext('0')

        // 3. assert
        verify(exactly = 1) { repo.setupPeriodicUpdate() }
        screenStateTestObserver.assertNoErrors()

        screenStateTestObserver.lastValue.apply {
            Assert.assertEquals("200", outputNumberString)
        }
    }

Description

  1. Arrange - we first setup the necessary objects

  2. Act - next, we make some actions.

While we run the following:

        // 2. act
        populateDbIfFirstTime.onNext(true)
        getLatestSelectedRateFlowable.offer(fakeRate)

        onNumpad1Click.onNext('1')
        onNumpad0Click.onNext('0')
        onNumpad0Click.onNext('0')

It is actually doing these:

  1. populateDbIfFirstTime.onNext(true) - seeded the db

  2. getLatestSelectedRateFlowable.offer(fakeRate) - taken the latest conversion rate from db

  3. onNumpad1Click.onNext('1'), onNumpad1Click.onNext('0'), onNumpad1Click.onNext('0') - click on 1, 0, 0 (100)

  4. Assert - finally, we check on the output:

        screenStateTestObserver.lastValue.apply {
            Assert.assertEquals("200", outputNumberString)
        }

Since the fake conversaion rate is set to 2.0, the output should be 200 when input is 100.

Debugging

Stetho library is used for debugging purpose. Network calls through OkHttp3 and SQLite Database can be viewed by accessing chrome://inspect/#devices on a chrome browser. This is an example of how the database look like in DevTools: devtool db debug screenshot.

Adaptive Icon

An adaptive icon is created using Sketch. The sketch file can be found in logo.sketch file at the root of this project.

Coding Style

.editorconfig is used in this project to make sure that the spacing and indentations are standardized, the editorconfig is obtained from ktlint project.