Vincent Tsen
Posted on December 17, 2021
In this article, I'm going to share my experience in completing Android Kotlin Developer Nanodegree projects.
As mentioned in my previous article - Cheated by Android Kotlin Developer Nanodegree Course from Udacity, there are total of 5 projects that you need to complete in order to graduate from the Android Kotlin Developer Nanodegree. Since I have completed all the projects, I think it may worth for me to share my thoughts on them.
All the projects come with a starter code. You basically complete these projects on your own based on the guided instructions. The reviewer will pass or fail your project submission based on project rubric (known as project specifications) and also provide feedback on your code. You can submit the projects as many as you want.
Note: I'm going to share my completed code in the following sections. Please use it as your reference only and do not copy my code to graduate from the Nanodegree program! :)
Course 1 Project: Build a Shoe Store Inventory App
- Starter code: nd940-android-kotlin-course1-starter
- Completed code: vinchamp77/Android_NanoDegree_ShoeStore
If you follow all the lessons (i.e. Build your First App, Layouts, App Navigation, Activity & Fragment Lifecycle, and App Architecture UI Layer) in this course, you shouldn't have any problem to complete this project. It is pretty straight forward except for listing screen (will be explained later).
There are a total of 5 screens here (i.e. login, welcome, instructions, listing, and detail screens). This is my version of designing the UI. It is up to you how you want to design it.
The login screen is just a placeholder. It doesn't really function as an actual login screen. The purpose of this project is to demonstrate that you know how to navigate from one fragment to another.
What I learned?
- ViewModel and Live Data (observe live data and setup click listener)
- Single Activity Architecture (implement multiple fragments and navigate between them with transition animation)
- SafeArgs (pass data between fragment using bundle)
-
Layout Design with sytles.xml (use
ConstraintLayout
and standardize the design style across different layouts) -
Data binding in layouts (implement both 1-way and 2-way data binding - e.g. in
EditView
) -
ScrollView (dynamically insert item into
ScrollView
during runtime) - Logout Menu (implement menu at the top left corner of the phone)
What I struggled?
Dynamic ScrollView
The hardest part in the project is the listing screen where you need to implement the ScrollView
which has LinearLayout
as child. Then, you need to manually insert the TextView
into the LinearLayout
. It is hard because it is not taught before in the lessons. I need to google this a bit to figure this out.
This is an example of ScrollView
+ LinearLayout
in the layout xml:
<!-- scroll view and linear layout are used so to that it supports more shoe listings -->
<ScrollView
android:id="@+id/scrollView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/add_shoe_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:id="@+id/shoe_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
</ScrollView>
I also created item_shoe.xml
layout to allow me easily to inflate the layout so I did not need to create the TextView
manually in code.
This is how my item_shoe.xml
looks like:
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/textView"
style="@style/DefaultTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
In the code, I inflate the item_shoe.xml
layout, update the text and add textView
into the LinearLayout
.
private fun addShoe(shoeName: String) {
val view = layoutInflater.inflate(R.layout.item_shoe, null)
view.textView.text = shoeName
binding.shoeList.addView(view.textView)
}
This is how I implemented the listing screen. It can be done in different way. Please also note that using RecycleView
(which will be learned in next project) is more practical than ScrollView
.
Course 2 Project: Build an Asteroid Radar
- Starter code: nd940-android-kotlin-c2-starter
- Completed code: vinchamp77/Android_NanoDegree_AsteroidRadar
Lessons in this course is probably the most useful and practical one in my opinion. The lessons include App Architecture (Persistence), RecycleView
, Connect to the Internet, Behind the Scenes, and Designing for Everyone.
What I learned?
- RecycleView (display list of asteroids)
- Retrofit (download data from internet)
- Moshi (convert JSON data to usable class data)
- Picasso (download and cache images)
- BindingAdapter(implement custom attribute in layout xml)
- Room Database (implement offline caching)
- Work Manager(schedule to update database every day)
- Accessibility (provide basic accessibility such as content description for texts and images)
What I struggled?
Parse JSON Data
The starter code provided the parseAsteroidsJsonResult()
function, but it did not explain why this is needed. This is because the Moshi
library is unable to convert the JSON data to the class data due to the received JSON format complexity.
So, the Retrofit
service API needs to return String
instead of List<Asteroid>
. Then, I convert the String
to JSON object using JSONObject()
. After that, I pass the JSON object to this helper function parseAsteroidsJsonResult()
to convert it to List<Asteroid>
.
See example below:
suspend fun getAsteroids() : List<Asteroid> {
val responseStr = retrofitService.getAsteroids("","", NetworkConstants.API_KEY)
val responseJsonObject = JSONObject(responseStr)
return parseAsteroidsJsonResult(responseJsonObject)
}
Issue with Glide?
The project mentioned Glide
has issue downloading the image, but it did NOT explain why. Thus, Picasso
is used in the starter code
So, I tried to replace Picasso
Picasso.with(imageView.context)
.load(it.url)
.into(imageView)
with Glide
Glide.with(imageView.context)
.load(it.url)
.into(imageView)
and everything still worked as expected. So, I do not really understand why. I guess it is likely the issue is no longer valid anymore?
Honestly, I wish they explain why and gives more detailed information.
Tip: By the way for the Asteroid API, you don't need request an API key. You can use the
DEMO_KEY
directly and commit it directly toGitHub
(which okay since this for demonstration purpose).
What will I do next?
This is the useful feedback that I get from the reviewer.
You can now also use the KotlinX Serialization library which is in the stable release.
This library supports JSON deserializing without Reflection based lookups and hence can save a lot of memory and time for large projects and also supports MultiPlatform.
The syntax is fairly easy but very powerful when deserializing large JSON.
So this is going to be my to-do list to understand how this library works.
[Updated - Aug 06, 2022]: I finally tried KotlinX Serialization but the result is NOT impressive at all. The claim it saves memory and time is simply NOT true. See my following article:
Course 3 Project: Design an App with Application Loading Status
- Starter code: nd940-c3-advanced-android-programming-project-starter
- Completed code: vinchamp77/Android_NanoDegree_CustomAppLoadingStatus
This course is all about notification and custom animation. The lessons include Using Notification, Creating Custom Views, Drawing on Canvas Objects, Clipping Canvas Objects, Android Property Animations, and Using MotionLayout
to Animate Android Apps.
This is how the app looks like:
What I learned?
-
Custom View (create custom view / button by inheriting
View
) -
Custom Attributes (create custom attributes using
withStyledAttributes()
) -
ValueAnimator (use
ValueAnimator
andPaint
to perform animations) -
Canvas (use
paint
to draw theCanvas
UI) -
MotionLayout (use
MotionLayout
andMotionScene
to perform animation) -
Notification (send notification and use
pendingIntent
to create the notification action)
What I struggled?
What I struggled is to make this learning stick. Maybe this the least used feature and I do not have chance to practice it? So, I write down some notes here for my future reference.
How to use ValueAnimator?
- Create a
ValueAnimator
- define returned value and duration
private val valueAnimator = ValueAnimator.ofInt(0, 360).setDuration(2000)
- Set up the animation callbacks -
progress
is the callback value which is used to perform drawing animation
valueAnimator.apply {
addUpdateListener {
progress = it.animatedValue as Int
invalidate()
}
repeatCount = ValueAnimator.INFINITE
repeatMode = ValueAnimator.RESTART
}
- Override
onDraw()
- useprogress
andpaint
to draw the canvas.
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
// loading button
paint.color = buttonLoadingColor
canvas?.drawRect(0f, 0f, widthSize * progress/360f, heightSize.toFloat(), paint)
}
How to start MotionLayout without user interaction?
The course shows you how to trigger the animation but it doesn't show you how to runs the animation automatically. So I added this app:autoTransition="animateToEnd"
in my MotionScene
file to auto start the animation when fragment is created.
<Transition
app:constraintSetStart="@+id/start"
app:constraintSetEnd="@+id/end"
app:duration="3000"
app:autoTransition="animateToEnd">
</Transition>
Include layout is used
The <include>
tag is used in all the layout xml files in the starter code which I wasn't really familiar with since this is not taught in the course too. However, it is not a big deal to understand it.
For example, you can see this in activity_main.xml
file:
<include layout="@layout/content_main" />
What will I do next?
Animation is my weak area but I probably won't focus in this area now. There are too many higher priority things to learn in the Android development.
Course 4 Project: Build a Location Reminder App
- Starter code: nd940-android-kotlin-c4-starter
- Completed code: vinchamp77/Android_NanoDegree_Location_Reminders
Note: Please read the README.md in the completed code. You need to setup the API Keys in order to run the app.
This is the worst project because the starter code has bunch of compilation errors. I attempted to fix but failed. I ended up just use the version provided by one of the mentors in the knowledge forum.
The lessons in this course include Wandering in Goolge Maps with Kotlin, Virtual Treasure Hunt with Geofences, Testing Basic, Intro to Test Double and Dependency Injection, Testing: Survey of Advanced Topics, and Implementing Login on Android with FirebaseUI.
This project use almost all the things that I learned in these lessons.
What I learned?
-
Firebase (setup Firebase project, register app with Firebase, add sign-in provider for authentication, download
google-services.json
toapp
folder directory) -
Google Map (enable Maps SDK for Android in Google Cloud Platform API & Services, setup API usage restriction to my app only, add
SupportMapFragment
in layout xml, implementOnMapReadyCallback
interface) -
Map Features (show current location, add marker, add POI, implement custom map style -
map_style.json
) - GeoFences (add geofencing request)
-
Location Permission (request
ACCESS_FINE_LOCATION
permission) - Device Location (auto resolve device location if disabled)
-
Unit Test (test
ViewModel
with fake data source) -
Instrumentation Test (use Espresso and Mockito to test the app UI and Fragments Navigation)
- Expresso - library to interact with UI and check UI state
- Mockito - library to check whether method is called
-
Koin (use
Koin
to inject dependencies into repository)
What I struggled?
Only My Location Layer is needed to show Current Location?
In theory, device location is not required to be enabled to show the current location in Google Map. Only the My Location Layer needs to be enabled to do that.
I'm not able verify this scenario. In my emulator, the current location will be shown correctly only if I enable the device location. If I disable the device location, it doesn't work.
So, I posted the question to the mentor in the Udacity knowledge forum and he ran fine with my code in his emulator. So, I have no ideas why?
One thing I don't understand is when you try to run a "Google Map" in your phone, it does explicitly require user to enable device location. Maybe device location is needed after all?
Koin Dependency Injection
The test code uses Koin
but the course doesn't teach you anything about Koin
. The course only teaches you constructor depedency injection. To be honest, I tried to study the code and I did not 100% understand it.
What will I do next?
Custom Login Screen
Instead of the default login screens of FirebaseUI, we can customize them. I will need to learn how to customize it. The resource here is for my reference.
RequestPermission Contract
There are 2 ways to request permission. We can either manage the request code ourselves or use the RequestPermission contract (allow the system to manage the permission request code for you). The current code manages the request code ourselves.
Because using the RequestPermission contract simplifies the logic, it's recommended that we use it when possible. So I gave this a try, I implemented this RequestPermission Contract already in my Final Capstone project and it worked great!
Hilt & Dagger Depedency Injection
I am totaly not familar in this area. Instead of Koin
(it seems a bit harder to understand), I will try to understand Hilt & Dagger first since there is pretty well official documentation here.
[Updated - Feb 06, 2022]: I have tried Hilt and documented the steps to implement it here.*
Final Capstone Project: Design and Build an Android Application
- Starter code: nd940-cap-advanced-android-programming-project-starter
- Completed code: vinchamp77/USElectionInfo
Note: Please read the README.md in the completed code. It requires you to setup the API Key in order to run the app.
This is the final capstone project. You have 2 options. First option is implement a custom app. If you choose this option, you need to submit a design document. Second option is implement a Political Preparedness app.
I chose second option - Political Preparedness app. I renamed the app to US Election Info.
What I learned?
What I learned here are pretty much have already been covered in the previous courses. So there isn't any thing new here.
-
Fragment Navigation (implement navigate graph, implement
Parcelable
object with@Parcelize
annotation to pass data between fragments) - MVVM Architecture (implement clean architecture using Repository acting as Model entry)
-
Motion Layout (hide the form while dragging up the
RecycleView
) - Retrofit (connect to and consume data from a remote data source such as a RESTful API)
- Glide (load network resources, such as Bitmap Images, dynamically and on-demand)
- Room Database (store data locally on the device for use between application sessions and/or offline use)
- Location (request location permission and resolve device location setting if it is turned off)
[Updated - Apr 30, 2023]: For MVVM architecture, you can refer to the following article.
What I struggled?
Code does Not Compile
The code doesn't compile and has many errors. I found troublesome to fix them. So I decided to build the app from scratch and use the starter code as a reference.
In fact, this helped me a lot to understand the project. For example, I figured out mininum SDK 24 is required because of @color
resource reference in ballot_logo.xml
(which is only supported in SDK 24 and above). The starter code uses minimum SDK 26 which is technically unnecessary. It can be lower down to SDK 24.
I also reorganized the package / folder structure based on my own recommendation in my previous article - How to organize android package folder structure?
So, my folder / package structure looks like this:
Motion Layout does NOT really work as Intended
When dragging up the RecycleView
, the form supposes to drag up too like the following and vice-versa.
However, I took the short cut to just simply hide the form.
This is how I did in in the motion scene file (simplified version) by using android:visibility="gone"
attribute at the end ConstraintSet
<?xml version="1.0" encoding="utf-8"?>
<MotionScene
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ConstraintSet android:id="@+id/start">
<Constraint android:id="@id/search_title" />
</ConstraintSet>
<ConstraintSet android:id="@+id/end">
<Constraint android:id="@id/search_title"
android:visibility="gone" />
</ConstraintSet>
<Transition
app:constraintSetStart="@id/start"
app:constraintSetEnd="@id/end">
<OnSwipe app:dragDirection="dragUp"
app:touchAnchorId="@id/representatives_recycler_view" />
</Transition>
</MotionScene>
What will I do next?
I want to publish this app in Google Play store. In fact, I have already done it. You can check it out here!
[Updated - Jan 15, 2021]: I faced the app rejection issue (i.e. missing clear source of information) while I tried to publish. Here is what I needed to do to fix it.
Career Service
Once you have completed the 5 projects, you have one extracurricular, which consists of these 2 projects:
- Take 30 Minutes to Improve your LinkedIn
- Optimize Your GitHub Profile
There are some lessons in these 2 projects. At the end of the project, you're going to submit your LinkedIn / GitHub profile to the reviewer for feedback. You don't need to resubmit them. One submission is considered passed.
These projects are optional but you need to manually opt out. I gave it a try, and I'm going to share my views on them.
Most of the stuff here is common sense. So, I'm going to share what is new to me or something that I can improve on?
Take 30 Minutes to Improve your LinkedIn
What I learned?
- Use present tense for current duties, past tense for prior duties and accomplishments. What I have before is all present tenses.
- There is a project section on LinkedIn, which I wasn't aware previously. You need to manually add them into your profile.
What will I do next?
- Fix wording to include the correct tense.
- Update project section with my Android development projects
- Reduce my skills section and put the most relevant one and use optimized keywords. I have too many of them which are not applicable anymore.
Feedback that I get from my reviewer is not very valuable because most of the stuff is pretty generic or already been covered in the lessons. What I learned above is mainly part of the course content. I did not learn any extra from the reviewer.
Optimize Your GitHub Profile
What I learned?
- Having a great README is important. I probably knew this already but haven't got chance to improve it.
- Commit graph that shows as many as green squares significant. Example of good commit graph:
What will I do next?
- Improve README in my GitHub, enroll to this free course from Udacity: Writting READMEs
- Collaborate with others and contribution to open source - I'm not sure if I can do this. It seems a bit hard. However, this definitely will be my goal.
The reviewer gave me a lot of feedback. He even read my article in this blog and provided his opinion. A lot of resources have been shared by him and these resources are not part of the lesson which is good.
My Final Thoughts
- The issue with all these started code projects is the code and libraries are outdated. So what I did the first thing, is updating the started code to use the latest libraries and fix all the warnings. It is basically all the update recommendations by Android Studio.
I managed to do this for all projects except for course 4 project. It has too many legacy codes which makes the upgrade almost impossible. You can try that, but it is going to waste your time.
- I commit my initial working version into
GitHub
using Android Studio. If you don't know how to do this, you can refer to How to update Android Studio project to GitHub? - I always perform incremental commit for every successful run. This way when things go wrong, I can always rollback to my previous working version.
- My suggestion is, don't look at the solution or completed code and work on the starter code directly. Then, you compare your solution with mine or other students. This way you learn more and make your knowledge stick!
- If you get stuck, ask the mentor (if you enroll in this program).
- Overall, the projects provided by Android Kotlin Developer Nanodegree are well-structured.
- However, the course is quite expensive. Whether it is worth it or not, it is up to you. You can check out my article - Is it Worth to Pay for Android Kotlin Developer Nanodegree?
I hope this review is useful.
Originally published at https://vtsen.hashnode.dev.
Posted on December 17, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
September 16, 2023