How can I make interface methods internal with Kotlin? - android

I wrote a simple SwipeLayout library. My goal is to publish it via GitHub for everyone, including myself, to use.
I encountered a problem when I decided to encapsulate some methods. I was hoping for an internal modifier to kick in, and solve the problem, but I can't find a proper way to do it.
Here are some code and explanation:
class SwipeLayout (...) : FrameLayout(...),
BackgroundViewsVisibilityController, ... {
private val backgroundController = BackgroundController(
this //(BackgroundViewsVisibilityController)
)
override fun revealLeftUnderView() {...}
override fun hideLeftUnderView() {...}
override fun revealRightUnderView() {...}
override fun hideRightUnderView() {...}
}
interface BackgroundViewsVisibilityController {
fun revealLeftUnderView()
fun revealRightUnderView()
fun hideLeftUnderView()
fun hideRightUnderView()
}
These are the methods to hide from users. How can I best achieve it?

internal tells the compiler that this interface, class or function can only be called from the current module. When you release a library, that internal interface can not be called (without using reflection) from your library.
You want to make SwipeLayout public (for third party usage) so you can not make the interface BackgroundViewsVisibilityController internal at the same time, because your class is extending that interface.
If you really want that clients can not call the functions from your interface, consider not having an interface (as an interface is usually there for a caller to be used) but making those functions private in SwipeLayout:
class SwipeLayout (...) : FrameLayout(...)
private fun revealLeftUnderView() {...}
private fun hideLeftUnderView() {...}
private fun revealRightUnderView() {...}
private fun hideRightUnderView() {...}
}
More information about Kotlin Visibility Modifiers

Related

Seperating an Activity's Views and it's logic

I'm refactoring an activity that had grown too large. Ideally what I am trying to accomplish is to have my activity initialize all my view and set Listeners. Than off load the logic to a helper class. I pretty sure I would like to do this with an interface. But that's were I'm stuck.
For example, let have classes Main and MainHelper. Main has a CardView and a button. The button will show the cardview.
MainHelper is what has the interface and Main implements it?
How do I update views from MainHelper?
Is there a better approach to what I'm trying to accomplish?
class MainActivity : AppCompatActivity(), MainHelper.MainActivityHelper {
private lateinit var btn: Button = findViewById(R.id.btn)
private lateinit var menu: CardView = findViewById(R.id.menu)
override fun onCreate(savedInstanceState: Bundle?) {
btn.setOnClickListener { v: View? -> handleBtn()}
}
override fun handleBtn() {}
}
class MainHelper: AppCompatActivity() {
interface MainActivityHelper {
fun handleBtnM() {
menu.visibility = View.VISIBLE
}
}
Instead of using this Helper class, I would recommend that you use a more modern and better tested architecture pattern like Model-View-ModelView (MVVM). If so, you can leverage Android Jetpack's Architecture Component to help you better organize and separate concerns as explained here: https://developer.android.com/jetpack/guide.
Here's a more in-depth explanation of Android's ViewModel implementation: https://developer.android.com/topic/libraries/architecture/viewmodel.

Using koin in Multi-module application

Hi in a multi modules app, I am loading child modules using loadKoinModules() and unloading it using unloadKoinModules() in feature module my code looks like
class FeatureActivity:AppCompatActivity(){
private val loadFeatures by lazy { loadKoinModules(featureModule) }
private fun injectFeatures() = loadFeatures
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
injectFeatures()
}
override fun onDestroy() {
super.onDestroy()
unloadKoinModules(featureModule)
}
}
Everything works fine but problem start when another instance on same activity is loaded. While current activity is in background. App crash due to error below
org.koin.error.BeanOverrideException: Try to override definition with Factory
Is there a way to avoid this error
It is somehow correct what you are doing, you can unload dynamically as you do this is why unloadKoinModules has been added link
but why aren't you unloading onStop? according to android lifecycle and what you want to do, you have to unload in onStop
When activity gets focus onCreate will occur (and you will load modules), later when activity loses focus, onStop will occurs (and you will unload modules) and the circle between the events...
class FeatureActivity:AppCompatActivity(){
private val loadFeatures by lazy { loadKoinModules(featureModule) }
private fun injectFeatures() = loadFeatures
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
injectFeatures()
}
override fun onStop() {
super.onStop()
unloadKoinModules(featureModule)
}
}
Koin won't let to you redefine an already existing definition (type,name,path …​). You will run into an error.
You need to allow definition override :-
val featureModule = module {
// override for this definition
single<yourType>(override=true) { YourClass() }
}
ALSO you can override on module level instead of overriding on definition level only:-
val featureModule = module(override=true) {
single<yourType> { YourClass() }
}
Important:-
Order matters when listing modules and overriding definitions. You must have your overriding definitions in last of your module list.
Some possibilities:
Load your feature module in the top-level application level and don't scope it to any activity lifecycle.
Add a reference-counting wrapper around your module load/unload so the module is not reloaded if it is already loaded, and it is only unloaded when the usage count is zero. (You can simplify this by not caring about unloading and change the count to just a "initialised" boolean.)

What is the best way to set my delegate variable (inner class vs. this)?

So, I have a following BLEClient class and I'm currently setting deviceDelegate using this keyword
class BLEClient(val device: Device) : Client, DeviceDelegate {
init {
device.deviceDelegate = this
}
// client interface methods
override fun connect() {...}
override fun disconnect() {...}
override fun send() {...}
// device delegate interface methods
override fun didSend() { ... }
override fun didConnect() { ... }
override fun didReceive() { ... }
}
I was wondering if this is the best way to do it, since I could also use an inner class for DeviceDelegate instead of implementing interface directly, in my eyes this would make the code more readable and simple?
The class would look like this:
class BLEClient(val device: Device) : Client {
init {
device.deviceDelegate = DeviceDelegateInner()
}
/* client interface methods */
override fun connect() {...}
override fun disconnect() {...}
override fun send() {...}
inner class DeviceDelegateInner() : DeviceDelegate {
override fun didSend() { }
override fun didConnect() { }
override fun didReceive() { }
}
}
Are there any significant drawbacks if I set my delegate using an inner class instead of implementing an interface directly and setting it using this keyword?
What would you guys prefer? Which way is better?
It really depends on your use-case, and I'm not familiar with DeviceDelegate at all. But I'll give it a try.
Using inner class provides better encapsulation and separation of concerns.
Also, implementing less interfaces makes your class a bit easier to reason about, for the same reasons above.
You can also argue that the second approach is more "composition over inheritance".
I would use the first approach only if you see a lot of duplication in your inner class, eg:
class BLEClient(val device: Device) : Client {
fun b() { }
inner class DeviceDelegateInner() : DeviceDelegate {
override fun a() = b()
}
}

I'm wondering why I have to write "private" from SUPER NEW

I'm just studying Kotlin about MVP Model.
after I made MainPresenter class, I connected with interface, mainContract
and I faced a problem. I fixed it up but I can't explain it by myself so can you explain why I have to add 'private'?
MainPresenter
class mainPresenter : mainContract.Presenter {
private lateinit var mainModel: mainModel
private lateinit var view: mainContract.View
// here's private is that i ask u
override fun setView(view: mainContract.View) {
}
override fun setModel(model: mainModel) {
}
override fun onConfirm() {
}
}
//here is interface
interface mainContract {
interface Presenter {
fun setView(view: mainContract.View)
fun setModel(model: mainModel)
fun onConfirm()
}
interface View {
fun showButtonText(text: String)
}
}
This has been answered here:
https://javarevisited.blogspot.com/2012/03/private-in-java-why-should-you-always.html
Or even here: https://softwareengineering.stackexchange.com/questions/143736/why-do-we-need-private-variables
A short answer: "none of your business how this class works, here is what you have to know". If you are writing a single app, there is no real harm in making everything public. However - if you are writing a library, you might want to keep the same user facing functions, and modify non-user facing functions - internal API.

How to ensure ViewModel#onCleared is called in an Android unit test?

This is my MWE test class, which depends on AndroidX, JUnit 4 and MockK 1.9:
class ViewModelOnClearedTest {
#Test
fun `MyViewModel#onCleared calls Object#function`() = mockkObject(Object) {
MyViewModel::class.members
.single { it.name == "onCleared" }
.apply { isAccessible = true }
.call(MyViewModel())
verify { Object.function() }
}
}
class MyViewModel : ViewModel() {
override fun onCleared() = Object.function()
}
object Object {
fun function() {}
}
Note: the method is protected in superclass ViewModel.
I want to verify that MyViewModel#onCleared calls Object#function. The above code accomplished this through reflection. My question is: can I somehow run or mock the Android system so that the onCleared method is called, so that I don't need reflection?
From the onCleared JavaDoc:
This method will be called when this ViewModel is no longer used and will be destroyed.
So, in other words, how do I create this situation so that I know onCleared is called and I can verify its behaviour?
In kotlin you can override the protected visibility using public and then call it from a test.
class MyViewModel: ViewModel() {
public override fun onCleared() {
///...
}
}
I've just created this extension to ViewModel:
/**
* Will create new [ViewModelStore], add view model into it using [ViewModelProvider]
* and then call [ViewModelStore.clear], that will cause [ViewModel.onCleared] to be called
*/
fun ViewModel.callOnCleared() {
val viewModelStore = ViewModelStore()
val viewModelProvider = ViewModelProvider(viewModelStore, object : ViewModelProvider.Factory {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T = this#callOnCleared as T
})
viewModelProvider.get(this#callOnCleared::class.java)
//Run 2
viewModelStore.clear()//To call clear() in ViewModel
}
TL;DR
In this answer, Robolectric is used to have the Android framework invoke onCleared on your ViewModel. This way of testing is slower than using reflection (like in the question) and depends on both Robolectric and the Android framework. That trade-off is up to you.
Looking at Android's source...
...you can see that ViewModel#onCleared is only called in ViewModelStore (for your own ViewModels). This is a storage class for view models and is owned by ViewModelStoreOwner classes, e.g. FragmentActivity. So, when does ViewModelStore invoke onCleared on your ViewModel?
It has to store your ViewModel, then the store has to be cleared (which you cannot do yourself).
Your view model is stored by the ViewModelProvider when you get your ViewModel using ViewModelProviders.of(FragmentActivity activity).get(Class<T> modelClass), where T is your view model class. It stores it in the ViewModelStore of the FragmentActivity.
The store is clear for example when your fragment activity is destroyed. It's a bunch of chained calls that go all over the place, but basically it is:
Have a FragmentActivity.
Get its ViewModelProvider using ViewModelProviders#of.
Get your ViewModel using ViewModelProvider#get.
Destroy your activity.
Now, onCleared should be invoked on your view model. Let's test it using Robolectric 4, JUnit 4, MockK 1.9:
Add #RunWith(RobolectricTestRunner::class) to your test class.
Create an activity controller using Robolectric.buildActivity(FragmentActivity::class.java)
Initialise the activity using setup on the controller, this allows it to be destroyed.
Get the activity with the controller's get method.
Get your view model with the steps described above.
Destroy the activity using destroy on the controller.
Verify the behaviour of onCleared.
Full example class...
...based on the question's example:
#RunWith(RobolectricTestRunner::class)
class ViewModelOnClearedTest {
#Test
fun `MyViewModel#onCleared calls Object#function`() = mockkObject(Object) {
val controller = Robolectric.buildActivity(FragmentActivity::class.java).setup()
ViewModelProviders.of(controller.get()).get(MyViewModel::class.java)
controller.destroy()
verify { Object.function() }
}
}
class MyViewModel : ViewModel() {
override fun onCleared() = Object.function()
}
object Object {
fun function() {}
}
For Java, if you create your test class in the same package (within the test directory) as of the ViewModel class (here, MyViewModel), then you can call onCleared method from the test class; since protected methods are also package private.

Categories

Resources