Make your own sliding menu on Android tutorial - Part 1

Issue #151

This post was from long time ago when I did Android


I canโ€™t deny that Facebook is so amazing, they made trends and people want to follow. That is the case of the sliding menu.

Searching many threads on SO, like these create android Sliding Menu like Facebook,gmail, Android sliding menu similar to the one on facebook, Android - Sliding menu with sub menu โ€ฆ they will mostly introduce you to some good sliding menu libraries, like this SlidingMenu and some tutorials on how to use it. And of course, not forget to mention the Navigation Drawer that shipped with the Android SDK

If you are a do-it-yourself guy like me, then here is the tutorial for you. It is mostly based on the following Youtube videos and guides

Read More

Understanding suspend function in Kotlin Coroutine in Android

Issue #123

Getting to know Coroutine

From https://kotlinlang.org/docs/reference/coroutines.html

To continue the analogy, await() can be a suspending function (hence also callable from within an async {} block) that suspends a coroutine until some computation is done and returns its result:

From https://kotlinlang.org/docs/tutorials/coroutines-basic-jvm.html

We are using the delay() function thatโ€™s like Thread.sleep(), but better: it doesnโ€™t block a thread, but only suspends the coroutine itself. The thread is returned to the pool while the coroutine is waiting, and when the waiting is done, the coroutine resumes on a free thread in the pool.

await() can not be called outside a coroutine, because it needs to suspend until the computation finishes, and only coroutines can suspend in a non-blocking way

What does suspend function mean in Kotlin Coroutine ๐Ÿค”

https://stackoverflow.com/questions/47871868/what-does-suspend-function-mean-in-kotlin-coroutine

Iโ€™m reading Kotlin Coroutine and know that it is based on suspend function. But what does suspend mean?

Coroutine or function gets suspended?

From https://kotlinlang.org/docs/reference/coroutines.html

Basically, coroutines are computations that can be suspended without blocking a thread

I heard people often say โ€œsuspend functionโ€. But I think it is the coroutine who gets suspended because it is waiting for the function to finished? โ€œsuspendโ€ usually means โ€œcease operationโ€, in this case the coroutine is idle.

Should we say the coroutine is suspended ?

Which coroutine gets suspended?

From https://kotlinlang.org/docs/reference/coroutines.html

To continue the analogy, await() can be a suspending function (hence also callable from within an async {} block) that suspends a coroutine until some computation is done and returns its result:

1
2
3
4
5
6
async { // Here I call it the outer async coroutine
...
// Here I call computation the inner coroutine
val result = computation.await()
...
}

๐Ÿค” It says โ€œthat suspends a coroutine until some computation is doneโ€, but coroutine is like a lightweight thread. So if the coroutine is suspended, how can the computation is done ?

We see await is called on computation, so it might be async that returns Deferred, which means it can start another coroutine

1
2
3
4
5
fun computation(): Deferred<Boolean> {
return async {
true
}
}

๐Ÿค” The quote say that suspends a coroutine. Does it mean suspend the outer async coroutine, or suspend the inner computation coroutine?

Does suspend mean that while outer async coroutine is waiting (await) for the inner computation coroutine to finish, it (the outer async coroutine) idles (hence the name suspend) and returns thread to the thread pool, and when the child computation coroutine finishes, it (the outer async coroutine) wakes up, takes another thread from the pool and continues?

The reason I mention the thread is because of https://kotlinlang.org/docs/tutorials/coroutines-basic-jvm.html

The thread is returned to the pool while the coroutine is waiting, and when the waiting is done, the coroutine resumes on a free thread in the pool

Understanding async

From https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md

Conceptually, async is just like launch. It starts a separate coroutine which is a light-weight thread that works concurrently with all the other coroutines. The difference is that launch returns a Job and does not carry any resulting value, while async returns a Deferred โ€“ a light-weight non-blocking future that represents a promise to provide a result later. You can use .await() on a deferred value to get its eventual result, but Deferred is also a Job, so you can cancel it if needed.

1
2
3
4
5
6
7
8
fun main(args: Array<String>) = runBlocking<Unit> {
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
}

There is a laziness option to async using an optional start parameter with a value of CoroutineStart.LAZY. It starts coroutine only when its result is needed by some await or if a start function is invoked. Run the following example that differs from the previous one only by this option:

1
2
3
4
5
6
7
8
fun main(args: Array<String>) = runBlocking<Unit> {
val time = measureTimeMillis {
val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
}

What is the difference between launch/join and async/await in Kotlin coroutines

https://stackoverflow.com/a/48079738/1418457

I find this guide https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md to be useful. I will quote the essential parts

๐Ÿฆ„ coroutine

Essentially, coroutines are light-weight threads.

So you can think of coroutine as something that manages thread in a very efficient way.

๐Ÿค launch

1
2
3
4
5
6
7
8
fun main(args: Array<String>) {
launch { // launch new coroutine in background and continue
delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
println("World!") // print after delay
}
println("Hello,") // main thread continues while coroutine is delayed
Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive
}

So launch starts a background thread, does something, and returns a token immediately as Job. You can call join on this Job to block until this launch thread completes

1
2
3
4
5
6
7
8
fun main(args: Array<String>) = runBlocking<Unit> {
val job = launch { // launch new coroutine and keep a reference to its Job
delay(1000L)
println("World!")
}
println("Hello,")
job.join() // wait until child coroutine completes
}

๐Ÿฆ† async

Conceptually, async is just like launch. It starts a separate coroutine which is a light-weight thread that works concurrently with all the other coroutines. The difference is that launch returns a Job and does not carry any resulting value, while async returns a Deferred โ€“ a light-weight non-blocking future that represents a promise to provide a result later.

So async starts a background thread, does something, and returns a token immediately as Deferred.

1
2
3
4
5
6
7
8
fun main(args: Array<String>) = runBlocking<Unit> {
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
}

You can use .await() on a deferred value to get its eventual result, but Deferred is also a Job, so you can cancel it if needed.

So Deferred is actually a Job. See https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/index.html

1
interface Deferred<out T> : Job (source)

๐Ÿฆ‹ async is eager by default

There is a laziness option to async using an optional start parameter with a value of CoroutineStart.LAZY. It starts coroutine only when its result is needed by some await or if a start function is invoked.

Understand Kotlin Coroutines on Android (Google I/Oโ€™19)

https://www.youtube.com/watch?v=BOHK_w09pVA

cor

BuddyBuild and gradle.properties

Issue #109

People advise against storing keys inside build.gradles. We should store them on 1Password and populate our gradle.properties, so donโ€™t track this file in git. Here is .gitignore file

1
2
3
4
5
6
7
8
*.iml

/build
/gradle.properties
/local.properties

.gradle
.idea

There are several ways to help BuddyBuild know about our gradle.properties

1. Using Environment variables

But when configuring the project on BuddyBuild, it complains about key not found. The solution is to use Environment variables

key

Then in your build.gradle, you can

1
buildConfigField 'String', 'MY_KEY', System.getenv("MY_KEY") ?: MY_KEY

This is because gradle does not know about environment variables. The System.getenv("MY_KEY") is for BuddyBuild, and the default MY_KEY is for gradle.properties.

Next is to remove this duplication. We can use Groovy Binding. build.gradle does the import import groovy.lang.Binding automatically for us

1
2
3
4
5
6
7
8
String environmentKey(variable) {
for (Object var : binding.variables) {
if (var.value == variable) {
return System.getenv(var.key) ?: variable
}
}
return ""
}
1
buildConfigField 'String', 'MY_KEY', environmentKey(MY_KEY)

2. Using Secured File ๐Ÿ‘

BuddyBuild allows us to define Secured File, here we can upload our gradle.properties

secure files

And we can use Prebuild script to copy this secured file to our project. BuddyBuild suggests using buddybuild_prebuild.sh but then build fails in Build file '/tmp/sandbox/workspace/app/build.gradle'

So, create a script called buddybuild_postclone.sh

1
2
3
#!/usr/bin/env bash

cp $BUDDYBUILD_SECURE_FILES/gradle.properties $BUDDYBUILD_WORKSPACE/gradle.properties

Communication between Fragment and Activity

Issue #108

Thereโ€™s always need for communication, right ๐Ÿ˜‰ Suppose we have OnboardingActivity that has several OnboardingFragment. Each Fragment has a startButton telling that the onboarding flow has finished, and only the last Fragment shows this button.

Here are several ways you can do that

1. EventBus ๐Ÿ™„

Nearly all articles I found propose this https://github.com/greenrobot/EventBus, but I personally donโ€™t like this idea because components are loosely coupled, every component and broadcast can listen to event from a singleton, which makes it very hard to reason when the project scales

1
data class OnboardingFinishEvent()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class OnboardingActivity: AppCompatActivity() {
override fun onStart() {
super.onStart()

EventBus.getDefault().register(this)
}

override fun onStop() {
EventBus.getDefault().unregister(this)
super.onStop()
}

@Subscribe(threadMode = ThreadMode.MAIN)
fun onOnboardingFinishEvent(event: OnboardingFinishEvent) {
// finish
}
}
1
2
3
4
5
6
7
8
9
class OnboardingFragment: Fragment() {
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

startButton.onClick {
EventBus.getDefault().post(OnboardingFinishEvent())
}
}
}

Read more

2. Otto ๐Ÿ™„

This https://github.com/square/otto was deprecated in favor of RxJava and RxAndroid

3. RxJava ๐Ÿ™„

We can use simple PublishSubject to create our own RxBus

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import io.reactivex.Observable
import io.reactivex.subjects.PublishSubject

// Use object so we have a singleton instance
object RxBus {

private val publisher = PublishSubject.create<Any>()

fun publish(event: Any) {
publisher.onNext(event)
}

// Listen should return an Observable and not the publisher
// Using ofType we filter only events that match that class type
fun <T> listen(eventType: Class<T>): Observable<T> = publisher.ofType(eventType)
}
1
2
3
4
// OnboardingFragment.kt
startButton.onClick {
RxBus.publish(OnboardingFinishEvent())
}
1
2
3
4
5
6
7
8
// OnboardingActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

RxBus.listen(OnboardingFinishEvent::class.java).subscribe({
// finish
})
}

4. Interface

This is advised here Communicating with Other Fragments. Basically you define an interface OnboardingFragmentDelegate that whoever conforms to that, can be informed by the Fragment of events. This is similar to Delegate pattern in iOS ๐Ÿ˜‰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface OnboardingFragmentDelegate {
fun onboardingFragmentDidClickStartButton(fragment: OnboardingFragment)
}

class OnboardingFragment: Fragment() {
var delegate: OnboardingFragmentDelegate? = null

override fun onAttach(context: Context?) {
super.onAttach(context)

if (context is OnboardingFragmentDelegate) {
delegate = context
}
}

override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

startButton.onClick {
delegate?.onboardingFragmentDidClickStartButton(this)
}
}
}
1
2
3
4
5
6
class OnboardingActivity: AppCompatActivity(), OnboardingFragmentDelegate {
override fun onboardingFragmentDidClickStartButton(fragment: OnboardingFragment) {
onboardingService.hasShown = true
startActivity<LoginActivity>()
}
}

5. ViewModel

We can learn from Share data between fragments to to communication between Fragment and Activity, by using a shared ViewModel that is scoped to the activity. This is a bit overkill

1
2
3
class OnboardingSharedViewModel: ViewModel() {
val finish = MutableLiveData<Unit>()
}
1
2
3
4
5
6
7
8
9
10
class OnboardingActivity: AppCompatActivity(), OnboardingFragmentDelegate {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val viewModel = ViewModelProviders.of(this).get(OnboardingSharedViewModel::class.java)

viewModel.finish.observe(this, Observer {
startActivity<LoginActivity>()
})
}
}

Note that we need to call ViewModelProviders.of(activity) to get the same ViewModel with the activity

1
2
3
4
5
6
7
8
9
10
class OnboardingFragment: Fragment() {
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

val viewModel = ViewModelProviders.of(activity).get(OnboardingSharedViewModel::class.java)
startButton.onClick({
viewModel.finish.value = Unit
})
}
}

7. Lambda

Create a lambda in Fragment, then set it on onAttachFragment. It does not work for now as there is no OnboardingFragment in onAttachFragment ๐Ÿ˜ข

1
2
3
4
5
6
7
8
9
10
11
class OnboardingFragment: Fragment() {
var didClickStartButton: (() -> Unit)? = null

override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

startButton.onClick {
didClickStartButton?.invoke()
}
}
}
1
2
3
4
5
6
7
8
9
10
11
class OnboardingActivity: AppCompatActivity() {
override fun onAttachFragment(fragment: Fragment?) {
super.onAttachFragment(fragment)

(fragment as? OnboardingFragment).let {
it?.didClickStartButton = {
// finish
}
}
}
}

8. Listener in Bundle ๐Ÿ™„

Read more