In this episode on how to create android currency converter app using Apilayer from scratch, we are going to explore different options that will help us build a successful android currency converter app.
Android currency converter app is a great productive / utility app that informs mostly travelers and tourist the exchange rate between their local currency and the currency they will use when they traveled.
To fetch the currency rates between different currencies we are going to use Apilayer currency API. Apilayer has both free API and paid API. There are other free currency converter API but there are also limited in the amount of supported currencies.
The currency converter app also includes a travel expense features. This feature gives the users opportunity to estimate and plan their travel within budgeted amount.
If you are excited to learn how an application like this is develop, you can continue reading. Beside Android currency converter app, there are many apps we have created from scratch to help android learners build their own app.
Please note that the complete app source code is not free. you can follow and complete the tutorial by copying code pasted here. But if you need to completed project folder, then go to the end of this page to see more information about payment.
To get more insight on how the app works, we have uploaded a demo app in Google Play. You can download it and check it out. If you have any suggestions or idea on how the app can be improve, we will really appreciate it.
WHAT WE WILL LEARN FROM ANDROID IN BUILDING THIS APP
Although this is not a complete list, but we will like to highlight that in the course of creating currency converter app from scratch, we are going to cover / use the following android and third-party APIs.
- Network call – Retrofit2
- Local storage – Room database and SharedPreference
- Adapter Pattern
- LiveData and ViewModel
- Compound View
- Custom Themes
CURRENCY CONVERTER APP FEATURES
- Show 4 or less currency rates at once
- Supports up to 170 currencies in the world
- Switch currencies up and down
- Currency graph rate
- Currency alert – Notify user when there is a change in currency of interest
- Notification
- Settings
- Travel expenses
- Multiple theme support
- Share and more
HAND SKETCH OF THE ANDROID APP
ANDROID CURRENCY CONVERTER APP SCREENSHOTS
ANDROID CURRENCY CONVERTER VIDEO DEMO
SCOPE OF THIS EPISODE
Before we go down to coding, I have attached the image of the project folder structure. As can be seen in the image, each feature / page in the app is separated by its own folder.
The code sample we will go through here we focus on the core feature of the app which is currency converter.
If you need the complete source code, I will suggest you scroll down to see how to download the source code with its accompanying PDF complete tutorial guide.
1. CREATE A NEW ANDROID PROJECT
- Open Android Studio
- Go to file menu
- Select new
- Enter project name
- Enter activity name
- Keep other default settings
- Click on finish button to create a new android project
2. ADD LIBRARY DEPENDENCIES IN BUILD.GRADLE FILE
For the dependencies, we will use several android third party libraries to make our work much easier and saves time.
The dependency section of build.gradle is well commented. The comments give us an idea of what each or group of libraries do.
Open build.gradle file and paste the content below.
dependencies {
//support libraries
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0-alpha01'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha3'
implementation 'androidx.cardview:cardview:1.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
//circular image and currency picker
implementation 'com.github.midorikocak:currency-picker-android:1.1.9'
implementation 'de.hdodenhof:circleimageview:2.2.0'
//Preference
implementation 'com.fxn769:stash:1.3.2'
//Room persistence data
implementation "android.arch.persistence.room:rxjava2:1.1.1"
implementation 'androidx.room:room-common:2.1.0-alpha03'
implementation 'androidx.room:room-runtime:2.1.0-alpha03'
annotationProcessor "androidx.room:room-compiler:2.1.0-alpha03"
implementation 'android.arch.lifecycle:livedata:1.1.1'
//Network call - Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.squareup.retrofit2:converter-scalars:2.3.0'
implementation 'com.squareup.retrofit2:adapter-rxjava:2.4.0'
implementation "com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0"
//logging Networking call
implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
//Reactive
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
implementation 'io.reactivex.rxjava2:rxjava:2.2.4'
//Work
implementation "android.arch.work:work-runtime:1.0.0-beta01"
implementation "android.arch.work:work-firebase:1.0.0-alpha11"
//fast Adapter
implementation 'com.mikepenz:fastadapter:3.3.1'
//Animation Transition
implementation 'com.github.Binary-Finery:Bungee:master-SNAPSHOT'
//Graphing
implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0-alpha'
//Date library
implementation 'joda-time:joda-time:2.10.1'
//debug db
debugImplementation 'com.amitshekhar.android:debug-db:1.0.3'
//Runtime permission
implementation 'com.karumi:dexter:5.0.0'
//View binding
api "com.jakewharton:butterknife:$butterKnifeVersion"
annotationProcessor "com.jakewharton:butterknife-compiler:$butterKnifeVersion"
}
3. UPDATE STRINGS.XML
Open res folder > strings.xml and add the code below to it.
<resources>
<string name="app_name">Smart Currency Converter</string>
<string name="number_of_currencies">Number of Currencies</string>
<string name="customize_colours">CUSTOMIZE COLORS</string>
<string name="enable_night_mode">ENABLE NIGHT MODE</string>
<string name="enable_push_notifications">ENABLE PUSH NOTIFICATIONS</string>
<string name="invite_friends">INVITE FRIENDS</string>
<string name="share"> SHARE</string>
<string name="more_apps">MORE APPS</string>
<string name="rate">RATE</string>
<string name="contact_us">CONTACT US</string>
<string name="search">Search</string>
<string name="monthly_graph">Monthly Graph</string>
<string name="select_currency_alert_type">Select Currency Alert Type</string>
<string name="notify_me_when_my_set_rate_is_greater_or_less_than_the_current_rate">Notify me when my set rate is greater or less than the current rate</string>
<string name="notify_me_on_a_particular_set_date_and_time_in_the_future">Notify me on a particular set date and time in the future</string>
<string name="trip_name">Trip name</string>
<string name="trip_budget">Trip budget</string>
</resources>
4. UPDATE STYLES.XML
Open res folder > styles.xml and add the code below to it
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="colorText">@color/colorTextSecondary</item>
<item name="iconOne">@drawable/ic_system_update_blue_700_36dp</item>
<item name="iconTwo">@drawable/ic_share_blue_700_36dp</item>
</style>
<style name="darkTheme" parent="Theme.AppCompat">
<item name="colorPrimary">#33343B</item>
<item name="colorPrimaryDark">#252529</item>
<item name="android:windowBackground">@color/colorTextSecondary</item>
<item name="colorAccent">@color/colorWhite</item>
<item name="colorText">#ffffff</item>
<item name="iconOne">@drawable/ic_system_update_grey_300_36dp</item>
<item name="iconTwo">@drawable/ic_share_grey_300_36dp</item>
</style>
</resources>
5. UPDATE COLORS.XML
Open res folder > colors.xml and add the code below to it.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#3F51B5</color>
<color name="colorPrimaryDark">#303F9F</color>
<color name="colorAccent">#FF4081</color>
<color name="colorGrey">#e1e1e1</color>
<color name="colorWhite">#ffffff</color>
<color name="colorBlack">#000000</color>
<color name="colorDark">#333333</color>
<color name="colorLightGrey">#D3D3D3</color>
<color name="colorTextSecondary">#666666</color>
<color name="colorDivider">#C0C0C0</color>
<color name="colorBackground">#6498C2</color>
<color name="graphBackground">#d69090</color>
<color name="graphLineColor">#c85656</color>
</resources>
6. UPDATE ATTRS.XML
Open the res folder > attrs.xml and add the code below to it.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="DarkNightTheme">
<attr name="primaryTextColor" format="color"/>
<attr name="colorTextSecondary" format="color"/>
<attr name="dividerColor" format="color"/>
<attr name="backgroundCardColor" format="color"/>
<attr name="colorText" format="color"/>
<attr name="iconOne" format="integer"/>
<attr name="iconTwo" format="integer"/>
</declare-styleable>
</resources>
7. CREATE CUSTOM APPLICATION CLASS
In the root project directory, right click.
Go to New > Java Class > enter your class name. I will use CustomApp but feel free to use any name of your choice.
Open the created CustomApp.java file and add the code below to it.
import android.app.Application;
import com.fxn.stash.Stash;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class CustomApp extends Application {
private static final String TAG = CustomApp.class.getSimpleName();
private Gson gson;
private GsonBuilder gsonBuilder;
@Override
public void onCreate() {
super.onCreate();
Stash.init(this);
gsonBuilder = new GsonBuilder();
gson = gsonBuilder.create();
}
public Gson getGson() {
return gson;
}
}
8. CREATE THE SPLASH PAGE
The first page in the app is a splash or intro page which waits for 2 seconds before it loads the main currency page. I used SplashActivity for the default activity page when I created my android project.
Open SplashActivity.java file and add the code below.
public class SplashActivity extends AppCompatActivity {
private static final String TAG = SplashActivity.class.getSimpleName();
@Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
getWindow().setStatusBarColor(Color.TRANSPARENT);
}
setContentView(R.layout.activity_splash);
ActionBar actionBar = getSupportActionBar();
if(null != actionBar){
actionBar.hide();
}
Handler handler = new Handler();
handler.postDelayed(() -> {
Intent intent = new Intent(SplashActivity.this, CurrencyActivity.class);
startActivity(intent);
Bungee.slideLeft(SplashActivity.this);
finish();
}, Constants.SPLASH_TIME);
}
}
The layout file of the above activity will contain one ImageView and two Textviews. Open the activity_splash.xml file and paste the code below.
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/colorBackground"
tools:context=".SplashActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:orientation="vertical"
android:gravity="center"
android:padding="20dp"
tools:ignore="UseCompoundDrawables">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/currency_logo"
android:contentDescription="@string/app_name"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Smart Currency Converter"
android:textColor="@color/colorWhite"
android:textAllCaps="true"
android:layout_marginTop="10dp"
android:textSize="15sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:layout_marginTop="4dp"
android:textColor="@color/colorDivider"
android:text="From Inducesmile.com"/>
</LinearLayout>
</FrameLayout>
9. CREATE CURRENCYACTIVITY PAGE
The SplashActivity page will load the CurrencyPage which is the core page of the app.
Create a new package under main project package. I will name it currencypage. Right click on the package, go to New menu > Android > Empty Activity and click ok.
In the open activity dialog box, name your activity CurrencyActivity.java or any name of your choice. Leave other options and click the finish button.
I decided to use LinearLayout for the grid part of the UI. The issue with it is that it contains much code when compared with ConstraitLayout.
Let first create the layout file to get a better understand of what we planned to achieve.
Go to res > layout > activity_currency.xml and add the following code snippet to it.
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".currencypage.CurrencyActivity">
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorPrimaryDark"
android:clickable="false"
android:orientation="vertical">
<com.inducesmile.smartcurrencyconverter.customview.CurrencyBoxView
android:id="@+id/first_currecncy"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</com.inducesmile.smartcurrencyconverter.customview.CurrencyBoxView>
<com.inducesmile.smartcurrencyconverter.customview.CurrencyBoxView
android:id="@+id/second_currecncy"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</com.inducesmile.smartcurrencyconverter.customview.CurrencyBoxView>
<com.inducesmile.smartcurrencyconverter.customview.CurrencyBoxView
android:id="@+id/third_currecncy"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</com.inducesmile.smartcurrencyconverter.customview.CurrencyBoxView>
<com.inducesmile.smartcurrencyconverter.customview.CurrencyBoxView
android:id="@+id/fourth_currecncy"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</com.inducesmile.smartcurrencyconverter.customview.CurrencyBoxView>
</androidx.appcompat.widget.LinearLayoutCompat>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_above="@+id/bottom_layout"
android:orientation="vertical">
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginEnd="1dp"
android:layout_marginRight="1dp"
android:layout_weight="1"
android:orientation="vertical">
<!--Inner wrapper for buttons-->
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/seven_btn"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="1dp"
android:layout_weight="1"
android:fontFamily="@font/roboto_light"
android:gravity="center"
android:text="7"
android:textSize="30sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/colorDivider" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/four_btn"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="1dp"
android:layout_weight="1"
android:fontFamily="@font/roboto_light"
android:gravity="center"
android:text="4"
android:textSize="30sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/colorDivider" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/one_btn"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="1dp"
android:layout_weight="1"
android:fontFamily="@font/roboto_light"
android:gravity="center"
android:text="1"
android:textSize="30sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/colorDivider" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/point_btn"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="1dp"
android:layout_weight="1"
android:fontFamily="@font/roboto_light"
android:gravity="center"
android:text="."
android:textSize="30sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
<!--vertical divider -->
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="@color/colorDivider" />
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginEnd="1dp"
android:layout_marginRight="1dp"
android:layout_weight="1"
android:orientation="vertical">
<!--Inner wrapper for buttons-->
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/eight_btn"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="1dp"
android:layout_weight="1"
android:fontFamily="@font/roboto_light"
android:gravity="center"
android:text="8"
android:textSize="30sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/colorDivider" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/five_btn"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="1dp"
android:layout_weight="1"
android:fontFamily="@font/roboto_light"
android:gravity="center"
android:text="5"
android:textSize="30sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/colorDivider" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/two_btn"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="1dp"
android:layout_weight="1"
android:fontFamily="@font/roboto_light"
android:gravity="center"
android:text="2"
android:textSize="30sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/colorDivider" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/zero_btn"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="1dp"
android:layout_weight="1"
android:fontFamily="@font/roboto_light"
android:gravity="center"
android:text="0"
android:textSize="30sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
<!--vertical divider -->
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="1dp"
android:layout_height="match_parent"
android:background="@color/colorDivider" />
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginEnd="1dp"
android:layout_marginRight="1dp"
android:layout_weight="1"
android:orientation="vertical">
<!--Inner wrapper for buttons-->
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/nine_btn"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="1dp"
android:layout_weight="1"
android:fontFamily="@font/roboto_light"
android:gravity="center"
android:text="9"
android:textSize="30sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/colorDivider" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/six_btn"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="1dp"
android:layout_weight="1"
android:fontFamily="@font/roboto_light"
android:gravity="center"
android:text="6"
android:textSize="30sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/colorDivider" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/three_btn"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="1dp"
android:layout_weight="1"
android:fontFamily="@font/roboto_light"
android:gravity="center"
android:text="3"
android:textSize="30sp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/colorDivider" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/del_btn"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginBottom="1dp"
android:layout_weight="1"
android:fontFamily="@font/roboto_light"
android:gravity="center"
android:text="DEL"
android:textSize="30sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical">
<!--Inner wrapper for buttons-->
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#C0C0C0"
android:orientation="vertical">
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/layout_rotate"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/switch_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/app_name"
app:srcCompat="@drawable/ic_autorenew_grey_600_48dp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/layout_number_select"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/switch_menu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/app_name"
app:srcCompat="@drawable/ic_menu_grey_600_48dp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/layout_graph"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/currency_graph"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/app_name"
app:srcCompat="@drawable/ic_show_chart_grey_600_48dp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/btn_notification"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/currency_notification"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/app_name"
app:srcCompat="@drawable/ic_notifications_active_grey_600_48dp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/btn_more_menu"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/currency_settings"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/app_name"
app:srcCompat="@drawable/ic_more_horiz_grey_600_48dp" />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/bottom_layout"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_alignParentBottom="true"
android:gravity="center"
android:background="@color/colorBackground"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/travel_expenses"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Travel Expenses"
android:textColor="@color/colorWhite"
android:drawablePadding="4dp"
android:textSize="15sp"
android:fontFamily="@font/roboto_light"
android:drawableTop="@drawable/ic_local_airport_blue_900_24dp"/>
</androidx.appcompat.widget.LinearLayoutCompat>
</RelativeLayout>
</androidx.appcompat.widget.LinearLayoutCompat>
If everything works well for you then you will see a UI similar to the image below.
Now open the CurrencyActivity.java and paste the code snippet below in the class.
public class CurrencyActivity extends AppCompatActivity implements IMenuSwitch, CurrencyBoxView.OnWrapperClickListener{
private static final String TAG = CurrencyActivity.class.getSimpleName();
@BindView(R.id.zero_btn)
AppCompatTextView buttonZero;
@BindView(R.id.one_btn)
AppCompatTextView buttonOne;
@BindView(R.id.two_btn)
AppCompatTextView buttonTwo;
@BindView(R.id.three_btn)
AppCompatTextView buttonThree;
@BindView(R.id.four_btn)
AppCompatTextView buttonFour;
@BindView(R.id.five_btn)
AppCompatTextView buttonFive;
@BindView(R.id.six_btn)
AppCompatTextView buttonSix;
@BindView(R.id.seven_btn)
AppCompatTextView buttonSeven;
@BindView(R.id.eight_btn)
AppCompatTextView buttonEight;
@BindView(R.id.nine_btn)
AppCompatTextView buttonNine;
@BindView(R.id.point_btn)
AppCompatTextView buttonDot;
@BindView(R.id.del_btn)
AppCompatTextView buttonDel;
@BindView(R.id.switch_layout)
AppCompatImageView switchLayout;
@BindView(R.id.switch_menu)
AppCompatImageView switchMenu;
@BindView(R.id.currency_graph)
AppCompatImageView currencyGraph;
@BindView(R.id.currency_notification)
AppCompatImageView currencyNotification;
@BindView(R.id.currency_settings)
AppCompatImageView currencySettings;
@BindView(R.id.travel_expenses)
AppCompatTextView travelExpenses;
@BindView(R.id.first_currecncy)
CurrencyBoxView firstCurrency;
@BindView(R.id.second_currecncy)
CurrencyBoxView secondCurrency;
@BindView(R.id.third_currecncy)
CurrencyBoxView thirdCurrency;
@BindView(R.id.fourth_currecncy)
CurrencyBoxView fourthCurrency;
private String firstValue = "1";
private CustomMenuDialog customMenuDialog;
private CurrencyViewModel currencyViewModel;
private AppApiHelper appApiHelper;
private CurrencyViewHelper currencyViewHelper;
private int indexSelected;
private Gson gson;
private StoreHelper storeHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
boolean newNightMode = LocalStore.getIsNightMode();
setTheme(newNightMode ? R.style.darkTheme : R.style.AppTheme);
Log.d(TAG, "Status Mode " + newNightMode);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_currency);
ButterKnife.bind(this);
ActionBar actionBar = getSupportActionBar();
if(null != actionBar){
actionBar.hide();
}
Dexter.withActivity(this)
.withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)
.withListener(new MultiplePermissionsListener() {
@Override
public void onPermissionsChecked(MultiplePermissionsReport report) {
if(!report.areAllPermissionsGranted()){
Toast.makeText(CurrencyActivity.this, "You need to grant all permission to use this app features", Toast.LENGTH_SHORT).show();
}
}
@Override
public void onPermissionRationaleShouldBeShown(List<com.karumi.dexter.listener.PermissionRequest> permissions, PermissionToken token) {
}
})
.check();
gson = ((CustomApp)getApplication()).getGson();
appApiHelper = new AppApiHelper();
currencyViewModel = new CurrencyViewModel(appApiHelper);
currencyViewHelper = new CurrencyViewHelper(this);
storeHelper = new StoreHelper();
// set initial number of currencies to show if not is set yet
int currencyShowing = LocalStore.getNumOfCurrenciesDisplay();
if(currencyShowing < 2){
//starting app for the first time
LocalStore.setNumOfCurrenciesDisplay(2);
}
initStartCurrencies(currencyShowing);
customMenuDialog = new CustomMenuDialog(CurrencyActivity.this);
customMenuDialog.setOnIMenuSwitch(this);
//attach interface method
firstCurrency.setOnWrapperClickListener(this);
secondCurrency.setOnWrapperClickListener(this);
thirdCurrency.setOnWrapperClickListener(this);
fourthCurrency.setOnWrapperClickListener(this);
//detect change in currency
observeCurrencyValueChange();
}
@OnClick(R.id.point_btn)
public void clickPoint(View view) {
changeValueOfFirstCurrency(view);
}
@OnClick(R.id.one_btn)
public void clickOne(View view) {
changeValueOfFirstCurrency(view);
}
@OnClick(R.id.two_btn)
public void clickTwo(View view) {
changeValueOfFirstCurrency(view);
}
@OnClick(R.id.three_btn)
public void clickThree(View view) {
changeValueOfFirstCurrency(view);
}
@OnClick(R.id.four_btn)
public void clickFour(View view) {
changeValueOfFirstCurrency(view);
}
@OnClick(R.id.five_btn)
public void clickFive(View view) {
changeValueOfFirstCurrency(view);
}
@OnClick(R.id.six_btn)
public void clickSix(View view) {
changeValueOfFirstCurrency(view);
}
@OnClick(R.id.seven_btn)
public void clickSeven(View view) {
changeValueOfFirstCurrency(view);
}
@OnClick(R.id.eight_btn)
public void clickEight(View view) {
changeValueOfFirstCurrency(view);
}
@OnClick(R.id.nine_btn)
public void clickNine(View view) {
changeValueOfFirstCurrency(view);
}
@OnClick(R.id.zero_btn)
public void clickZero(View view) {
changeValueOfFirstCurrency(view);
}
@OnClick(R.id.del_btn)
public void clickDel(View view) {
deleteLastText(view);
}
private void changeValueOfFirstCurrency(View view){
AppCompatTextView buttonClicked = (AppCompatTextView)view;
if(firstValue.equals("0")){
firstValue = "";
}
firstValue += buttonClicked.getText().toString();
firstCurrency.setCurrencyAmount(firstValue);
if(!firstValue.endsWith(".")){
makeCurrencyApiLayerCall();
}
}
private void deleteLastText(View view){
String formattedInput = "";
if(firstValue.length() > 1){
formattedInput = firstValue.substring(0, firstValue.length() - 1);
firstValue = formattedInput;
}else{
firstValue = "0";
}
firstCurrency.setCurrencyAmount(firstValue);
makeCurrencyApiLayerCall();
}
@OnClick(R.id.switch_layout)
public void switchCurrencyRowVertically(){
Toast.makeText(CurrencyActivity.this, "Vertical switch has occurred", Toast.LENGTH_SHORT).show();
}
@OnClick(R.id.currency_graph)
public void showCurrencyGraph(View view){
saveOpenCurrencyData();
Intent graphIntent = new Intent(CurrencyActivity.this, CurrencyGraphActivity.class);
startActivity(graphIntent);
Bungee.slideLeft(CurrencyActivity.this);
finish();
}
//Action menu button events
@OnClick(R.id.currency_settings)
public void onShowSettings(View view){
Intent settingsIntent = new Intent(CurrencyActivity.this, SettingsActivity.class);
startActivity(settingsIntent);
Bungee.slideLeft(CurrencyActivity.this);
finish();
}
@OnClick(R.id.currency_notification)
public void onCurrencyChangeNotification(View view){
saveOpenCurrencyData();
NotificationDialog notificationDialog = new NotificationDialog(CurrencyActivity.this);
notificationDialog.selectCurrencyAlertType();
}
@OnClick(R.id.travel_expenses)
public void onTravelExpenses(View view){
saveOpenCurrencyData();
Intent settingsIntent = new Intent(CurrencyActivity.this, TravelExpensesActivity.class);
startActivity(settingsIntent);
Bungee.slideLeft(CurrencyActivity.this);
finish();
}
@OnClick(R.id.switch_menu)
public void onMenuSwitch(View view){
customMenuDialog.selectMenuOptions();
}
@Override
public void onSwitchMenuClick(int position) {
initStartCurrencies(position);
LocalStore.setNumOfCurrenciesDisplay(position);
}
private void openCloseCurrencyBox(CurrencyBoxView currencyBoxView){
if(!isCurrencyBoxClosed(currencyBoxView)){
showCurrencyBox(currencyBoxView);
}
}
private void closeCurrencyBox(CurrencyBoxView currencyBoxView){
if(isCurrencyBoxClosed(currencyBoxView)){
hideCurrencyBox(currencyBoxView);
}
}
private void showCurrencyBox(CurrencyBoxView currencyBoxView){
currencyBoxView.setVisibility(View.VISIBLE);
}
private void hideCurrencyBox(CurrencyBoxView currencyBoxView){
currencyBoxView.setVisibility(View.GONE);
}
private boolean isCurrencyBoxClosed(CurrencyBoxView currencyBoxView){
return currencyBoxView.getVisibility() != View.GONE;
}
private void initStartCurrencies(int position){
if(position == 2){
closeCurrencyBox(thirdCurrency);
closeCurrencyBox(fourthCurrency);
int newHeight = (int)CommonUtils.convertDpToPixel(100, CurrencyActivity.this);
firstCurrency.setNewHieght(newHeight);
secondCurrency.setNewHieght(newHeight);
}
if(position == 3){
openCloseCurrencyBox(thirdCurrency);
closeCurrencyBox(fourthCurrency);
int newHeight = (int)CommonUtils.convertDpToPixel(90, CurrencyActivity.this);
firstCurrency.setNewHieght(newHeight);
secondCurrency.setNewHieght(newHeight);
thirdCurrency.setNewHieght(newHeight);
}
if(position == 4){
openCloseCurrencyBox(thirdCurrency);
openCloseCurrencyBox(fourthCurrency);
int newHeight = (int)CommonUtils.convertDpToPixel(80, CurrencyActivity.this);
firstCurrency.setNewHieght(newHeight);
secondCurrency.setNewHieght(newHeight);
thirdCurrency.setNewHieght(newHeight);
fourthCurrency.setNewHieght(newHeight);
}
}
@Override
public void onWrapperClicked(CurrencyBoxView view) {
int clickPosition = 0;
if(view.equals(firstCurrency)){
clickPosition = 1;
}
if(view.equals(secondCurrency)){
clickPosition = 2;
}
if(view.equals(thirdCurrency)){
clickPosition = 3;
}
if(view.equals(fourthCurrency)){
clickPosition = 4;
}
//save state
saveState();
Intent currencyIntent = new Intent(CurrencyActivity.this, ListCurrencyActivity.class);
currencyIntent.putExtra(Constants.CLICK_POSITION, clickPosition);
startActivity(currencyIntent);
Bungee.slideLeft(CurrencyActivity.this);
}
//Save current currency states
private void saveState(){
List<CurrencyModel> showingMoney = currencyViewHelper.getListOfOpenCurrencies(firstCurrency, secondCurrency, thirdCurrency, fourthCurrency);
CommonUtils.convertListObjectToString(gson, showingMoney);
}
@Override
protected void onStart() {
super.onStart();
Bundle bundle = getIntent().getExtras();
gson = ((CustomApp)getApplication()).getGson();
indexSelected = IntentBundle.returnIntValue(getIntent().getExtras(), Constants.CLICK_POSITION);
String stringOfObject = IntentBundle.returnStringValue(bundle, Constants.OBJECT_STORE);
if(!stringOfObject.equals("")){
CurrencyAdapter currencyAdapter = CommonUtils.convertObjectToString(gson, stringOfObject);
int flag = currencyAdapter.getMoneyFlag();
String code = currencyAdapter.getMoneyCode();
String name = currencyAdapter.getMoneyName();
//float amount = 0
Log.d(TAG, "Country Code " + code);
//data from state storage
String modelString = LocalStore.getStoredState();
List<CurrencyModel> showingMoney = gson.fromJson(modelString, new TypeToken<List<CurrencyModel>>(){}.getType());
if(indexSelected == 1){
//todo not yet implemented
float storedAmount = showingMoney.get(0).getAmount();
CurrencyModel replaceModel = new CurrencyModel(flag, name, code, storedAmount);
Objects.requireNonNull(showingMoney).set(0, replaceModel);
displayCurrenciesFromStateStore(showingMoney);
//make api call
makeCurrencyApiLayerCall();
}else{
CurrencyModel replaceModel = new CurrencyModel(flag, name, code, 0);
Objects.requireNonNull(showingMoney).set(indexSelected - 1, replaceModel);
currencyViewModel.updateCurrencyRates().setValue(showingMoney);
//make api call
singleCurrencyApiCall(showingMoney, code, indexSelected);
}
}else{
if(!LocalStore.getStoredState().equals("")){
// Load currencies from storage
List<CurrencyModel> currModels = storeHelper.loadModelsOnAppLoad(gson);
displayCurrenciesFromStateStore(currModels);
float storedAmount = currModels.get(0).getAmount();
firstValue = "";
firstValue = CommonUtils.removeTailingZero(storedAmount);
Log.d(TAG, "Current value of first value " + firstValue + " From state " + currModels.get(0).getAmount());
}else{
Log.d(TAG, "On start error");
}
}
}
// Observe currency changes
private void observeCurrencyValueChange(){
currencyViewModel.updateCurrencyRates().observe(this, new Observer<List<CurrencyModel>>() {
@Override
public void onChanged(List<CurrencyModel> currencyModels) {
displayCurrenciesFromStateStore(currencyModels);
}
});
}
//display currencies
private void displayCurrenciesFromStateStore(List<CurrencyModel> currencyModels) {
int showingCurrencies = LocalStore.getNumOfCurrenciesDisplay();
if(showingCurrencies == 2){
displayCurrencyDetails(firstCurrency, currencyModels.get(0));
displayCurrencyDetails(secondCurrency, currencyModels.get(1));
}
if(showingCurrencies == 3){
displayCurrencyDetails(firstCurrency, currencyModels.get(0));
displayCurrencyDetails(secondCurrency, currencyModels.get(1));
displayCurrencyDetails(thirdCurrency, currencyModels.get(2));
}
if(showingCurrencies == 4){
displayCurrencyDetails(firstCurrency, currencyModels.get(0));
displayCurrencyDetails(secondCurrency, currencyModels.get(1));
displayCurrencyDetails(thirdCurrency, currencyModels.get(2));
displayCurrencyDetails(fourthCurrency, currencyModels.get(3));
}
}
//Make single currency rate network work
private void singleCurrencyApiCall(List<CurrencyModel> models, String destination, int clickIndex){
String source = getSourceCurrency();
currencyViewModel.convertCurrencyUsingApiRemote(models, source, destination, clickIndex);
}
//Make API call to fetch currency rates
private void makeCurrencyApiLayerCall(){
String source = getSourceCurrency();
String destination = getDestinationCurrency();
//extract currency view content
List<CurrencyModel> showingMoney = currencyViewHelper.getListOfOpenCurrencies(firstCurrency, secondCurrency, thirdCurrency, fourthCurrency);
//Make api layer call
currencyViewModel.convertCurrencyUsingApiRemote(showingMoney, source, destination, 0);
}
private String getSourceCurrency(){
return firstCurrency.getCountryLanguageCode().getText().toString();
}
//Return currencies based on the number of open currencies
private String getDestinationCurrency(){
int showingCurrencies = LocalStore.getNumOfCurrenciesDisplay();
String destination = "";
if(showingCurrencies == 2){
destination = secondCurrency.getCountryLanguageCode().getText().toString();
}
if(showingCurrencies == 3){
destination = secondCurrency.getCountryLanguageCode().getText().toString() + "," +
thirdCurrency.getCountryLanguageCode().getText().toString();
}
if(showingCurrencies == 4){
destination = secondCurrency.getCountryLanguageCode().getText().toString() + "," +
thirdCurrency.getCountryLanguageCode().getText().toString() + "," + fourthCurrency.getCountryLanguageCode().getText().toString();
}
return destination;
}
//Update compound view
private void displayCurrencyDetails(CurrencyBoxView view, CurrencyModel item){
if(item.getMoneyFlag() != -1){
view.getCircleImageView().setImageResource(item.getMoneyFlag());
view.getCircleImageView().setTag(item.getMoneyFlag());
}
view.getCountryCurrencyCode().setText(item.getMoneyName());
view.getCountryLanguageCode().setText(item.getMoneyCode());
float mRate = item.getAmount();
view.getCurrencyAmount().setText(CommonUtils.removeTailingZero(mRate));
}
@Override
public void onBackPressed() {
// save current currencies state
saveOpenCurrencyData();
//super.onBackPressed();
//go to home
Intent startMain = new Intent(Intent.ACTION_MAIN);
startMain.addCategory(Intent.CATEGORY_HOME);
startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(startMain);
finish();
}
//Save open currencies before navigating
private void saveOpenCurrencyData() {
List<CurrencyModel> currencies = currencyViewHelper.getListOfOpenCurrencies(firstCurrency, secondCurrency, thirdCurrency, fourthCurrency);
storeHelper.saveOpenCurrenciesOnAppExist(gson, currencies);
}
}
10. CREATE CURRENCY LIST ACTIVITY FILE AND ADAPTER CLASS
Create a new activity like we did previously. I will name my activity ListCurrencyActivity. You are free to choose any name.
The activity contains a RecyclerView which is connected to an adapter that links the RecyclerView with the data source.
First open the activity_list_currency.xml and add a RecyclerView to it. Alternatively, you can copy the code below and paste it on the file.
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".listcurrency.ListCurrencyActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/currency_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
</androidx.recyclerview.widget.RecyclerView>
</androidx.appcompat.widget.LinearLayoutCompat>
Now, we need to add the code for the ListCurrencyActivity class. Instantiate the RecyclerView, attach an adapter and finally load its data.
Open ListCurrencyActivity.java file and add the code snippets below.
public class ListCurrencyActivity extends AppCompatActivity {
private static final String TAG = ListCurrencyActivity.class.getSimpleName();
@BindView(R.id.currency_list)
RecyclerView recyclerView;
private Context context = this;
private FastAdapter<CurrencyAdapter> fastAdapter;
private ItemAdapter<CurrencyAdapter> itemAdapter;
public OnSelectCurrency onSelectCurrency;
private int selectCurrencyIndex;
public interface OnSelectCurrency{
void OnCurrencySelected(CurrencyAdapter currencyAdapter);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
//Load theme
boolean newNightMode = LocalStore.getIsNightMode();
setTheme(newNightMode ? R.style.darkTheme : R.style.AppTheme);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list_currency);
ButterKnife.bind(this);
ActionBar supportActionBar = getSupportActionBar();
if (supportActionBar != null) {
supportActionBar.setTitle("Search Currency");
supportActionBar.setDisplayHomeAsUpEnabled(true);
}
//select currency index
selectCurrencyIndex = IntentBundle.returnIntValue(getIntent().getExtras(), Constants.CLICK_POSITION);
initRecyclerView();
itemAdapter.getItemFilter().withFilterPredicate(new IItemAdapter.Predicate<CurrencyAdapter>() {
@Override
public boolean filter(CurrencyAdapter item, CharSequence constraint) {
String userInput = String.valueOf(constraint);
return item.getMoneyCode().toLowerCase().startsWith(userInput);
}
});
}
public void setOnSelectCurrency(OnSelectCurrency onSelectCurrency) {
this.onSelectCurrency = onSelectCurrency;
}
private void initRecyclerView(){
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(ListCurrencyActivity.this);
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setHasFixedSize(true);
setUpRecyclerAdapter();
}
private void setUpRecyclerAdapter(){
itemAdapter = new ItemAdapter<>();
fastAdapter = FastAdapter.with(itemAdapter);
recyclerView.setAdapter(fastAdapter);
fastAdapter.withSelectable(true);
//multiple events mapping (click and select)
fastAdapter.withEventHook(new ClickEventHook<CurrencyAdapter>() {
@Nullable
@Override
public List<View> onBindMany(RecyclerView.ViewHolder viewHolder) {
List<View> clickableView = new ArrayList<>();
AppCompatImageView favoriteIcon = ((CurrencyAdapter.ViewHolder) viewHolder).getFavoriteCurrency();
AppCompatTextView codename = ((CurrencyAdapter.ViewHolder) viewHolder).getCurrencyName();
clickableView.add(favoriteIcon);
clickableView.add(codename);
return clickableView;
}
@Override
public void onClick(View v, int position, FastAdapter<CurrencyAdapter> fastAdapter, CurrencyAdapter item) {
if(v instanceof AppCompatImageView){
Toast.makeText(ListCurrencyActivity.this, "Start icon has been clicked ", Toast.LENGTH_SHORT).show();
}
if(v instanceof AppCompatTextView){
//convert item object to string
Gson gson = ((CustomApp)getApplication()).getGson();
String itemString = CommonUtils.convertObjectToString(gson, item);
Intent sendNewCurrencyIntent = new Intent(ListCurrencyActivity.this, CurrencyActivity.class);
sendNewCurrencyIntent.putExtra(Constants.OBJECT_STORE, itemString);
sendNewCurrencyIntent.putExtra(Constants.CLICK_POSITION, selectCurrencyIndex);
startActivity(sendNewCurrencyIntent);
Bungee.slideRight(ListCurrencyActivity.this);
finish();
}
}
});
getCurrencyDetailsFromAPI(itemAdapter);
}
private void getCurrencyDetailsFromAPI(ItemAdapter<CurrencyAdapter> itemAdapter){
List<CurrencyAdapter> allCurrencies = new ArrayList<>();
List<ExtendedCurrency> currencies = ExtendedCurrency.getAllCurrencies();
for (ExtendedCurrency currency : currencies){
Log.d(TAG, "Currency Name " + currency.getName() + " Currency Code " + currency.getCode());
allCurrencies.add(new CurrencyAdapter(currency.getFlag(), currency.getName(), currency.getCode(), false));
}
itemAdapter.add(allCurrencies);
}
@Override
public void onBackPressed() {
//super.onBackPressed();
Intent currencyIntent = new Intent(ListCurrencyActivity.this, CurrencyActivity.class);
startActivity(currencyIntent);
Bungee.slideRight(ListCurrencyActivity.this);
finish();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.search_menu_layout, menu);
MenuItem searchItem = menu.findItem(R.id.search);
SearchView searchView = (SearchView)searchItem.getActionView();
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override
public boolean onQueryTextSubmit(String query) {
Log.d(TAG, "Submit text : " + query + " Adapter Counter " + itemAdapter.getAdapterItemCount());
itemAdapter.filter(query);
Log.d(TAG, "Adapter Counter " + itemAdapter.getAdapterItemCount());
return true;
}
@Override
public boolean onQueryTextChange(String newText) {
Log.d(TAG, "New text : " + newText);
itemAdapter.filter(newText);
return true;
}
});
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id){
case android.R.id.home:
onBackPressed();
finish();
break;
case R.id.search:
break;
}
return super.onOptionsItemSelected(item);
}
}
11. CREATE CURRENCYADAPTER CLASS
Create a new java file and name it CurrencyAdapter.java. The class will contain a ViewHolder, setter and getter for its data object.
Open the class and add the code below to it.
public class CurrencyAdapter extends AbstractItem<CurrencyAdapter, CurrencyAdapter.ViewHolder>{
private static final String TAG = CurrencyAdapter.class.getSimpleName();
private static final int CURRENCY_LAYOUT_ID = 202;
private int moneyFlag;
private String moneyName;
private String moneyCodes;
private boolean isFavourite;
public CurrencyAdapter(int moneyFlag, String moneyName, String moneyCode, boolean isFavourite) {
this.moneyFlag = moneyFlag;
this.moneyName = moneyName;
this.moneyCodes = moneyCode;
this.isFavourite = isFavourite;
}
public int getMoneyFlag() {
return moneyFlag;
}
public String getMoneyName() {
return moneyName;
}
public String getMoneyCode() {
return moneyCodes;
}
@NonNull
@Override
public ViewHolder getViewHolder(View v) {
return new ViewHolder(v);
}
@Override
public int getType() {
return CURRENCY_LAYOUT_ID;
}
@Override
public int getLayoutRes() {
return R.layout.currency_item_layout;
}
public static class ViewHolder extends FastAdapter.ViewHolder<CurrencyAdapter>{
@BindView(R.id.currency_favorite)
AppCompatImageView favoriteCurrency;
@BindView(R.id.country_flag)
CircleImageView currencyFlag;
@BindView(R.id.currency_code)
AppCompatTextView currencyCode;
@BindView(R.id.currency_name)
AppCompatTextView currencyName;
public ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
@Override
public void bindView(CurrencyAdapter item, List<Object> payloads) {
int flag = item.getMoneyFlag();
if(flag > -1){
currencyFlag.setImageResource(flag);
currencyFlag.setTag(flag);
}
currencyCode.setText(item.getMoneyCode());
currencyName.setText(item.getMoneyName());
boolean inFavoriteList = item.isFavourite;
if(inFavoriteList){
favoriteCurrency.setImageResource(R.drawable.ic_star_yellow_700_24dp);
}else{
favoriteCurrency.setImageResource(R.drawable.ic_star_border_yellow_700_24dp);
}
}
@Override
public void unbindView(CurrencyAdapter item) {
currencyCode.setText(null);
currencyName.setText(null);
}
public AppCompatImageView getFavoriteCurrency() {
return favoriteCurrency;
}
public AppCompatTextView getCurrencyCode() {
return currencyCode;
}
public AppCompatTextView getCurrencyName() {
return currencyName;
}
}
}
12. CREATE A LAYOUT FOR THE ADAPTER LIST ITEM
Go to res folder > layout folder, right click and select layout file. In the open dialog, name the layout currency_item_layout. Open the layout file and add the code snippet below.
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/root_layout"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.LinearLayoutCompat
android:id="@+id/first_event"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:padding="12dp">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/country_flag"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:layout_margin="2dp"
android:contentDescription="@string/app_name"
android:src="@drawable/usaflag" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/currency_code"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="20dp"
android:gravity="center"
android:text="USD"
android:textColor="?attr/colorText"
android:textSize="22sp"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/currency_name"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="12dp"
android:layout_marginTop="4dp"
android:gravity="center"
android:text="US Dollar"
android:textColor="@color/colorDivider"
android:textSize="13sp" />
</androidx.appcompat.widget.LinearLayoutCompat>
<androidx.appcompat.widget.LinearLayoutCompat
android:layout_width="0dp"
android:layout_height="match_parent"
android:orientation="horizontal"
android:gravity="center|end"
android:paddingEnd="12dp"
android:layout_weight="1">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/currency_favorite"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="6dp"
android:padding="8dp"
android:src="@drawable/ic_star_border_yellow_700_24dp"
tools:ignore="RtlHardcoded" />
</androidx.appcompat.widget.LinearLayoutCompat>
</androidx.appcompat.widget.LinearLayoutCompat>
13. REGISTER AND GET A CURRENCY API KEY
Here we need to register and obtain our currency API key from Apilayer website. In the home page, click the currency API link to navigate to this page below.
Click on the SignUp free button
Enter your signup details and submit.
Copy your generated API key and put it in a save place.
CREATE A NEW JAVA CLASS
Create a new java file and name it Constants.java. This class will contain all our constant variables and keys.
Open the file and add the code below to it.
public class Constants {
private Constants() {
}
public static final int SPLASH_TIME = 2000;
public static final String DISPLAY_CURRENCY_COUNTER = "currency_counter";
public static final String NIGHT_MODE_STATUS = "night_mode_status";
public static final String OBJECT_STORE = "object_store";
public static final String CLICK_POSITION = "click_position";
public static final String BASE_URL = "https://apilayer.net/";
public static final String STORED_STATE = "stored_state";
public static final String SPLASH = "splash";
public static final String EXTRA_ID = "id";
public static final String CURRENT_RATE = "current_rate";
public static final String TARGET_RATE = "target_rate";
}
WHAT WE HAVE NOW
Like I said from the beginner, we will only cover the core feature of the currency converter app and the code above has achieved that. If you want to download the complete source code see the section below.
HOW TO DOWNLOAD THE COMPLETE SOURCE CODE
You can follow this tutorial and complete the tutorial with the code snippets provided in the tutorial.
The source code is not free to download because we spent a lot of time to create these apps from scratch and your little support will help us to even create more apps from scratch.
SOURCE CODE CUSTOMIZATION
If you like the source code and will also like to customize its look and feel or add and remove some features, kindly contact us using the comment section below or through our contact page.
Please note that we charge $20 per hour for customization.
SUMMARY
We have walk through the core feature of android currency converter and I guess you have the better insight on how to continue to improve your source code.
I will suggest you buy the source code to learn how other features were implemented.
If you have questions and suggestions, kindly use the comment box below to get in touch
I look forward in seeing you in our next build from scratch episode.