I have an android app (main) that creates an object -- in a singleton manner and is used throughout the application. The class is called NetworkFrame which is a common module in the project.
The main app has an activity that contains a fragment which exists in another module in the project (viewer). The viewer module is imported into the main app using Gradle. I need to pass the NetworkFrame object to the viewer fragment.
Both the module and the app both have NetworkFrame as a dependency.
The object is too detailed to use serializable or Parcel. I have looked into dependency injection, but I'm not sure if that's the correct use of it.
class MainApplication : Application() {
companion object {
var networkFrame: NetworkFrame? = null
}
}
class MainFragment : Fragment() {
...
if (MainApplication.networkFrame == null) {
MainApplication.networkFrame = networkFrame
}
viewModel.networkFrame = MainApplication.networkFrame
...
}
Above shows the creation of the object in the main application. I need to pass this object to the viewer fragment's networkFrame object.
class ViewerModel : ViewModel() {
var networkFrame: NetworkFrame? = null
}
What's the best way to get this object to the viewer fragment?
I think that you require an instance of NetworkFrame then I recommend to you create an interface that provides to you the instance
interface HasNetworkFrame {
fun getNetworkFrame(): NetworkFrame
}
This interface should be implemented on the application, something like this
class OneApplication : Application(), HasNetworkFrame{
fun getNetworkFrame(): NetworkFrame {
// return your object
}
}
And in your fragment can access via something like this
val frame = (requireContext().applicationContext as? HasNetworkFrame)?.getNetworkFrame()
However i think that this is not a good approach, take a look into solid principles and put focus on
Dependency Inversion Principle
Related
I'm not very clear about the best way to inject into a static methods helper class (lets say a Custom class).
I'm kinda new to Kotlin, and as I've learnt we can access a method statically in two ways:
Object class.
Class + companion object.
To start, I'm not sure which one is the most recommended one (if there is a best practice regarding this), but my "problem" arises when needing to inject dependencies into a static method class.
Let's go with a simple example:
I have a static methods class called AWUtils (not decided if it should be an object class or a class with companion object yet though, and this will most likely depend on the injection mechanism recommended) with the next method:
fun setAmpersand2Yellow(text2Replace: String, target: String): String {
return text2Replace.replace(
target, "<span style=\"color:" +
app.drawerFooterColor + ";\">" + target + "</span>"
)
}
Here, app is the instance of my AppSettings class which holds all app configuration so, as you see setAmpersand2Yellow needs AppSettings, and of course I would't pass it as a parameter by any means, so it's a AWUtils dependence.
Using AWUtils as a class with companion object for the static methods I cannot inject directly AppSettings into company object as far as I know (at least I cannot do constructor injection, let me know if I'm wrong) and if I inject into companion object parent class (AWUtils) constructor then I don't know how to access those dependences from the companion object itself (the child).
If I use fields injection in AWUtils as a class then it complains than lateinit field has not been initialised and I don't know how to deal with this, because as far as I know lateinit fields are initialised in onCreate, which does not exist in this kind of classes.
One other possibility is to use an object with fields and set the dependencies values from caller in a static way before calling the method, for example:
object AWUtils {
var app: AppSettings? = null
fun setAmpersand2Yellow(text2Replace: String, target: String): String {
return text2Replace.replace(
target, "<span style=\"color:" +
app.drawerFooterColor + ";\">" + target + "</span>"
)
}
}
#AndroidEntryPoint
class OtherClass
#Inject constructor(private val app: AppSettings) {
fun AnyFunction() {
var mystr = "whatever"
AWUtils.app = app
var yellowStr = AWUtils.setAmpersand2Yellow(myStr)
}
}
In the end, I'm not sure on how to supply dependencies to a static methods class and which form of "static" class should I choose.
Edit 1:
Apart from my ApSettings class, I need a context, like for example in this next isTablet method:
val isTablet: String
get() {
return ((context.resources.configuration.screenLayout
and Configuration.SCREENLAYOUT_SIZE_MASK)
>= Configuration.SCREENLAYOUT_SIZE_LARGE)
}
In the end, I need a context and my AppSettings (or any other custom classes) to be injected anyway in a class with static methods.
Edit 2:
I could do (from the activity):
AWUtils.context = this
AWUtils.app = app
var isTablet = AWUtils.isTablet
And it works, but rather to be in the need of assigning a value to two fields (or more) every time I need to call a static method, I would prefer the fields to be injected in any way.
That's what dependency injection is meant for, isn't it?
Edit 3: I'm starting to be fed up with Hilt, what is supposed would have been created to simplify our life, only makes our programming life much more complicated.
As you clarified in the comments, you want to have your utils class accessible in an easy way across your codebase, so this answer will focus on that and on your original questions.
I'm kinda new to Kotlin, and as I've learnt we can access a method statically in two ways: Object class or Class + companion object.
Kotlin does not have Java-style statics. One reasoning behind it was to encourage more maintainable coding practices. Static methods and static classes are also a nightmare for testing your code.
In Kotlin you would go with an object (but a class + companion object would work in the same way)
object AWUtils {
lateinit var appContext: Context
lateinit var appSettings: AppSettings
fun initialize(
appContext: Context,
appSettings: AppSettings,
// more dependencies go here
) {
this.appContext = appContext
this.appSettings = appSettings
// and initialize them here
}
val isTablet: Boolean
get() = ((appContext.resources.configuration.screenLayout
and Configuration.SCREENLAYOUT_SIZE_MASK)
>= Configuration.SCREENLAYOUT_SIZE_LARGE)
fun setAmpersand2Yellow(text2Replace: String, target: String): String {
return text2Replace.replace(
target, "<span style=\"color:" +
appSettings.drawerFooterColor + ";\">" + target + "</span>"
)
}
}
Since this object should be accessible across the whole application it should be initialized as soon as possible, so in Application.onCreate
#HiltAndroidApp
class Application : android.app.Application() {
// you can inject other application-wide dependencies here
// #Inject
// lateinit var someOtherDependency: SomeOtherDependency
override fun onCreate() {
super.onCreate()
// initialize the utils singleton object with dependencies
AWUtils.initialize(applicationContext, AppSettings())
}
Now anywhere in your app code you can use AWUtils and AppSettings
class OtherClass { // no need to inject AppSettings anymore
fun anyFunction() {
val mystr = "whatever"
val yellowStr = AWUtils.setAmpersand2Yellow(myStr)
// This also works
if (AWUtils.isTablet) {
// and this as well
val color = AWUtils.appSettings.drawerFooterColor
}
}
}
There is another way in Kotlin to write helper/util functions, called extension functions.
Your isTablet check might be written as an extension function like this
// This isTablet() can be called on any Configuration instance
// The this. part can also be omitted
fun Configuration.isTablet() = ((this.screenLayout
and Configuration.SCREENLAYOUT_SIZE_MASK)
>= Configuration.SCREENLAYOUT_SIZE_LARGE)
// This isTablet() can be called on any Resources instance
fun Resources.isTablet() = configuration.isTablet()
// This isTablet() can be called on any Context instance
fun Context.isTablet() = resources.isTablet()
With the above extension functions in place the implementation inside AWUtils would be simplified to
val isTablet: Boolean
get() = appContext.isTablet()
Inside (or on a reference of) any class that implements Context, such as Application, Activity, Service etc., you can then simply call isTablet()
class SomeActivity : Activity() {
fun someFunction() {
if (isTablet()) {
// ...
}
}
}
And elsewhere where Context or Resources are available in some way, you can simply call resources.isTablet()
class SomeFragment : Fragment() {
fun someFunction() {
if (resources.isTablet()) {
// ...
}
}
}
Edit 3: I'm starting to be fed up with Hilt, what is supposed would have been created to simplify our life, only makes our programming life much more complicated.
Yeah, Hilt is focusing on constructor injection and can only do field injection out-of-the-box in very limited cases, afaik only inside Android classes annotated with #AndroidEntryPoint and inside the class extending the Application class when annotated with #HiltAndroidApp.
Docs for #AndroidEntryPoint say
Marks an Android component class to be setup for injection with the standard Hilt Dagger Android components. Currently, this supports activities, fragments, views, services, and broadcast receivers.
If you feel that you need a lot of field injection, because you are working with "static"-like objects in Kotlin, consider using Koin instead of Hilt for your next project.
In Arrow Kt Documentation on Dependency Injection, the dependency is defined at the "Edge of the World" or in Android could be an Activity or a Fragment. So the given example is as follow:
import Api.*
class SettingsActivity: Activity {
val deps = FetcherDependencies(Either.monadError(), ActivityApiService(this))
override fun onResume() {
val id = deps.createId("1234")
user.text =
id.fix().map { it.toString() }.getOrElse { "" }
friends.text =
deps.getUserFriends(id).fix().getOrElse { emptyList() }.joinToString()
}
}
But now I'm thinking how could the SettingsActivity in the example could be unit tested? Since the dependency is created within the activity, it could no longer be changed for testing?
When using some other Dependency Injection library, this dependency definition is create outside of the class it will be used on. For example in Dagger, a Module class is created to define how the objects (dependencies) are created and an #Inject is used to "inject" the dependency defined inside the module. So now when unit testing the Activity, I just have to define a different module or manually set the value of the dependency to a mock object.
In Dagger you would create a Mock or Test class that you would #Inject instead of ActivityApiService. It is the same here.
Instead of:
class ActivityApiService(val ctx: Context) {
fun createId(): String = doOtherThing(ctx)
}
You do
interface ActivityApiService {
fun createId(): String
}
and now you have 2 implementations, one for prod
class ActivityApiServiceImpl(val ctx: Context): ActivityApiService {
override fun createId(): Unit = doOtherThing(ctx)
}
and another for testing
fun testBla() {
val api = object: ActivityApiService {
override fun createId(): String = "4321"
}
val deps = FetcherDependencies(Either.monadError(), api)
deps.createId("1234") shouldBe "4321"
}
or even use Mockito or a similar tool to create an ActivityApiService.
I have a couple of articles on how to decouple and unitest outside the Android framework that aren't Arrow-related. Check 'Headless development in Fully Reactive Apps' and the related project https://github.com/pakoito/FunctionalAndroidReference.
If your dependency graph becomes too entangled and you'd like some compile-time magic to create those dependencies, you can always create a local class in tests and #Inject the constructor there. The point is to decouple from things that aren't unitestable, like the whole Android framework :D
I am migrating an application to MVVM and clean architecture, and I am missing one part of the puzzle.
The problem domain:
List all applications on device and display them in the Fragment / Activity
A device app is represented by its package name:
data class DeviceApp(val packageName: String)
This is how the device apps are listed:
private fun listAllApplications(context: Context): List<DeviceApp> {
val ans = mutableListOf<DeviceApp>()
val packageManager: PackageManager = context.applicationContext.packageManager
val packages = packageManager.getInstalledApplications(PackageManager.GET_META_DATA)
for (applicationInfo in packages) {
val packageName = applicationInfo.packageName
ans.add(DeviceApp(packageName))
}
return ans
}
As I understand, calling listAllApplications() should be done in a UseCase inside the 'Domain Layer', which is called by a ViewModel.
However listAllApplications receives a Context, and the Domain Layer should be plain code only.
In clean architecture / MVVM, in which layer should I put listAllApplications(context)?
And more generally, how should the ViewModel interact with Android framework APIs that require Context (location, etc.)?
Domain Layer should be plain code only.
That's correct!, but in my opinion it's partially correct. Now considering your scenario you need context at domain level. You shouldn't have context at domain level but in your need you should either choose other architecture pattern or consider it as exceptional case that you're doing this.
Considering you're using context at domain, you should always use applicationContext in spite of activity context, because earlier persists through out process.
How should the ViewModel interact with android framework APIs that require Context (location, etc.)?
Whenever you need Context at ViewModel either you can provide it from UI as method parameter (I.e. viewModel.getLocation(context)) or else use AndroidViewModel as your parent class for ViewModel (it provides getApplication() public method to access context through out ViewModel).
All I would like to point you out is that make sure you don't accidentally hold any View/Context globally inside ViewModel/Domain Layer, because it can make catastrophe like memory leaking or crashes at worse.
You can solve this problem very cleanly with dependency-injection. If you aren't already using DI, you probably want to be, as it will greatly simplify your clean-architecture endeavours.
Here's how I'd do this with Koin for DI.
First, convert your usecase from a function to a class. This allows for constructor injection:
class ListAllApplications(private val context: Context) {
...
}
You now have a reference to context inside your usecase. Great! We'll deal with actually providing the value of context in a moment.
Now you're thinking... but aren't usecases meant to use reusable functions? What's the guy on about with usecases being classes?
We can leverage the miracle that is operator funs to help us here.
class ListAllApplications(private val context: Context) {
operator fun invoke(): List<DeviceApp> {
val ans = mutableListOf<DeviceApp>()
val packageManager: PackageManager = context.applicationContext.packageManager
val packages = packageManager.getInstalledApplications(PackageManager.GET_META_DATA)
for (applicationInfo in packages) {
val packageName = applicationInfo.packageName
ans.add(DeviceApp(packageName))
}
return ans
}
}
invoke is a special function which allows an instance of a class to be invoked as if it were a function. It effectively transforms our class into a function with an injectable constructor 🤯
And this allows us to continue to invoke our usecase in the ViewModel with the standard function invocation syntax:
class MyViewModel(private val listAllApplications: ListAllApplications): ViewModel {
init {
val res = listAllApplications()
}
}
Notice that our ListAllApplications usecase is also being injected into the constructor of MyViewModel, meaning that the ViewModel remains entirely unaware of Context.
The final piece of the puzzle is wiring all this injection together with Koin:
object KoinModule {
private val useCases = module {
single { ListAllApplications(androidContext()) }
}
private val viewModels = module {
viewModel { MyViewModel(get()) }
}
}
Don't worry if you've never used Koin before, other DI libraries will let you do similar things. The key is that your ListAllApplications instance is being constructed by Koin, which provides an instance of Context with androidContext(). Your MyViewModel instance is also being constructed by Koin, which provides the instance of ListAllApplications with get().
Finally you inject MyViewModel into the Activity/Fragment which uses it. With Koin that's as simple as:
class MyFragment : Fragment {
private val viewModel: MyViewModel by viewModel()
}
Et Voilà!
I try to create scope for a feature. I define a module like this.
val appModule = module {
scope(named("ARTIST_SCOPE")) {
scoped {
ArtistRepository(get())
}
scoped {
GetArtistsUseCase(get())
}
viewModel { ArtistViewModel(get()) }
}
}
My goal is to make ArtistRepository, GetArtistUseCase, and ArtistViewModel only accessible inside Artist Feature.
In my activity
class ArtistActivity : AppCompatActivity() {
private val artistScope = getKoin().createScope("artistScope", named("ARTIST_SCOPE"))
private val viewModel: ArtistViewModel by artistScope.viewModel(this)
...
}
My problem is when I leave this activity and return back to it.
I got this error.
org.koin.core.error.ScopeAlreadyCreatedException: A scope with id 'artistScope' already exists. Reuse or close it.
enter code here
How to reuse the existing scope?
or Am I implement the scope in the right way?
You want to use getOrCreateScope(). This will get an existing instance if you have one that isn't closed with the same scopeId, or it will create a new instance if it needs to.
I'm trying to change my app from having no design pattern to using MVP.
Originally I had the following code:
override fun onCreateInputView(): View {
//favoritesData is an instance variable, same with "Gson", "parser", "favorites", and "stringArrayListType"
favoritesData = File(filesDir, "favorites_data.json")
if (favoritesData.exists()) {
favorites = Gson.fromJson(parser.parse(FileReader(favoritesData)), stringArrayListType)
}
}
and
fun updateFavoritesFile() {
favoritesData.writeText(Gson.toJson(favorites))
}
After trying to use MVP I changed the code to:
class AnimeFaceKeyboardPresenter(val view : AnimeFaceKeyboardView, private val model : KeyboardModel = KeyboardModel()) : Presenter {
override fun onCreateInputView() {
model.favorites = view.loadFavoritesFile()
//At some point, call view.updateFavoritesFile(arrayListOf("test","test2"))
}
override fun onStartInputView() {
}
}
and the code in the activity itself to:
override fun loadFavoritesFile() : ArrayList<String> {
val favoritesData = File(filesDir, favoritesFileName)
var favorites = ArrayList<String>()
//"favorites" is no longer an instance variable
if (favoritesData.exists()) {
favorites = Gson.fromJson(parser.parse(FileReader(favoritesData)), stringArrayListType)
}
return favorites
}
override fun updateFavoritesFile(favorites: ArrayList<String>) {
File(filesDir, favoritesFileName).writeText(Gson.toJson(favorites))
}
override fun onCreateInputView(): View {
super.onCreateInputView()
presenter = AnimeFaceKeyboardPresenter(this)
presenter.onCreateInputView()
}
I'm not sure if I'm using MVP correctly, but if I am, how would I go about testing this code. For example - writing a test that calls updateFavoritesFile(arrayListOf("test1","test2")) and uses loadFavoritesFile() to check if the contents is as expected.
Well, you might want to relocate your file read and write to your model (they are associated with data which doesn't really belong in your view).
Then your test consists of instantiating your model object, and testing the methods which can be done without the view and presenter (simplifying the tests).
I would be very tempted to abstract your file as some form of "Repository" object that knows how to read and write strings (but you don't care where or how). You would pass the repository object to your model as a construction property. The advantage of this is that you can create a mock or fake Repository object which you can use to "feed" test data and examine written data, making testing that part of your model a little easier.
Don't forget, your view shouldn't have direct access to your model under MVP .. that would me more like MVC (one of the very few differences between MVP and MVC).