Intermodule (library projects) communication in android application - android

In the below shown diagram, I am having 3 modules(as android library) which extends the base "common components module" and all this 3 modules will be added to a single android application. All 3 modules are independent modules but when it comes as an application, it would require to share some data, launch other module and requires more inter-communication.
So can anyone let me know how we can implement the "Data Sharing Layer" and "Navigation Controller" in this kind of architecture?
Example: Module1 -> Login, Module2 -> Profile Management etc and there could be "n" number of modules based on the application need.

What you are looking for is basically a clean approach on how to communicate with other classes. There is not really a difference in whether or not they are in different modules.
The following sample describes how a LoginActivity could navigate to some profile activity. This is just a basic sample to be improved with what you actually need and intend to do!
Define your interfaces
Write interfaces of what you need. Your Login should be able to open a profile page? Well this sounds like it needs a LoginNavigator!
interface LoginNavigator {
void showProfile();
}
Include those interfaces in your shared components. There is not really a possibility to go without defining interfaces. You can make them more abstract or more fine grained, this is entirely up to you.
Declare your dependencies
Remember how your Login needs a LoginNavigator? The real problem is on how to supply it to your class. You should have a look at dependency injection, since there are frameworks liks dagger-2 that (could) make this easier. For now, we define an interface for a common component, so that we can retrieve the dependencies we need.
interface NavigatorProvider {
LoginNavigator provideNavigator();
}
You may guess it—this method is used to get the actual LoginNavigator that you can use to get the implementation of that interface. Usually you would just declare this dependency in the constructor, but since android is somewhat special you need to get it from somewhere yourself.
Provide your dependencies
The easiest way to go is to just have your application implement this interface (or hold an object that does).
class MyApp extends Application implements NavigatorProvider {
LoginNavigator provideNavigator() {
return new LoginNavigator() {
void showProfile() {
// just some sample code. You should probably not use an
// anonymous class
startActivity(new Intent(this, MyProfileActivity.class));
}
};
}
}
Again, you could also return an object that is implementing this interface. This is just a basic sample.
Use the interface. (And don't care about the implementation)
Now the dependency injection is nearly complete. We have an interface that we need, we have some way to provide the dependency, all that's left is to get it and use it.
class LoginActivity extends Activity {
LoginNavigator mNavigator;
void onCreate() {
// get the dependency
mNavigator = ((NavigatorProvider) getApplicationContext()).provideNavigator();
// use it where needed. (again, just sample code)
findShowProfileView().setOnClickListener(new OnClickListener() {
void onClick(View view) {
mNavigator.showProfile();
}
});
}
}
Now the dependency is provided, and ready to be used.
What this sample shows is how to basically use interfaces to decouple logic. You will still need some point of entry, since android does not allow to implement your own constructors—this is why the application class is used.

I found that solution using Local Broadcast which is implemented in Application Class and send event on Local Broadcast which is received in Application Class.
class AppApplication : Application() {
override fun onCreate() {
super.onCreate()
registerBroadcast()
}
private fun startProfileActivity() {
val intent = newIntent<MyProfileActivity>(this)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
this.startActivity(intent)
}
private fun registerBroadcast() {
LocalBroadcastManager.getInstance(this)
.registerReceiver(broadCastReceiver,IntentFilter(BROADCAST_VIEW_PROFILE))
}
private fun unregisterBroadcast() {
LocalBroadcastManager.getInstance(this)
.unregisterReceiver(broadCastReceiver)
}
private val broadCastReceiver = object : BroadcastReceiver() {
override fun onReceive(contxt: Context?, intent: Intent?) {
when (intent?.action) {
BROADCAST_VIEW_PROFILE -> {
startProfileActivity()
}
}
}
}
override fun onTerminate() {
super.onTerminate()
unregisterBroadcast()
}
}
When you send the event in an Application like this
private fun viewProfileEventSend() {
// Send Broadcast for view profile to `APP`
val intent = Intent(BROADCAST_VIEW_PROFILE)
LocalBroadcastManager.getInstance(requireContext()).sendBroadcast(intent)
}
Because your module doesn't need to get the instance of Application or any interface.

Related

How to scope a Usecase to a Feature / Activity in Koin

I'm maintaining a large app (mostly) using a "one feature one activity"-architecture.
Now i'd like to scope a usecase, so it lives as long as the activity, something like this:
// koin module
scope<MyFeatureActivity> {
viewModel { MyFeatureActivityViewModel() }
viewModel { MyFeatureFragmentAViewModel(usecase = get()) }
viewModel { MyFeatureFragmentBViewModel(usecase = get()) }
scoped { MyFeatureUseCase() }
}
// fragments
class FeatureAFragment: AppCompatDialogFragment(){
private val viewModel by viewModel<MyFeatureFragmentAViewModel>()
....
}
// activity
class MyFeatureActivity : ScopeActivity() { ... }
However, this doesn't work. When launching MyFeatureFragmentA from MyFeatureActivity it's throwing an Exception:
org.koin.core.error.NoBeanDefFoundException:
|- No definition found for class:'MyFeatureAViewModel'. Check your definitions!
What am i doing wrong?
Please note: I would not like to just skip scopes and make the usecase a single (or a factory), since it actually stores some data relevant to only this activity: The data should be kept while we're in this feature, but dismissed when leaving it.

How to pass an interface as parameter in koin

I'm so beginner in koin.
I have a method that named "MesheRepoImpl" that get an interface as parameter.
I know that I can't pass an interface to a method in Koin, so I created a class and extends that from the interface then I added that class in koin module, so I use the class as parameter for MesheRepoImpl.
But android studio gives me this error:
Caused by: org.koin.core.error.NoBeanDefFoundException: |- No definition found for class:'com.app.meshe.data.repo.MesheRepo'. Check your definitions!
This is my Di module:
val mesheModule =
module {
single { getInstance(androidContext()) }
single { MesheLocalDataSource() } //*
single { MesheRepoImpl(get()) } //**
factory { TaskViewModelFactory(get()) }
viewModel { TaskViewModel(get()) }
viewModel { RewardViewModel(get()) }
viewModel {MainViewModel()}
}
The 1 star line is my class that extends from the interface and the 2 stars line is the class that get interface as parameter.
How can I pass the interface as parameter, if I can't use a class?
Since there's still no answer, I'd advise you to consider going with
interface MesheRepo
class MeshoRepoImpl(): MeshoRepo
over your
interface MesheRepo
class MeshoRepoImpl(val IRepo: MeshoRepo)
So, just implement MeshoRepo over passing it as an argument to MeshoRepoImpl.
Trying to answer directly your question, you are able to define interfaces in Koin module and pass them, but you have to provide their implementations, as well:
val mesheModule = module {
single<MeshoRepo> { MeshoRepoImpl() }
single { MeshoRepoImpl(get()) } // <-- it's like a deadlock, so I still do not see any sense to pass an interface over implementing it
}
And, please, do not forget that an interface is not an object.

How to create an extension function with multiple receivers in Kotlin?

I want my extension function to have a couple of receivers. For example, I want function handle to be able to call methods of both CoroutineScope and Iterable instances:
fun handle() {
// I want to call CoroutineScope.launch() and Iterable.map() functions here
map {
launch { /* ... */ }
}
}
I thought this might work:
fun <T> (Iterable<T>, CoroutineScope).handle() {}
But it gives me an error:
Function declaration must have a name
I know that I can create the function with parameters, but
Is it possible to have multiple receivers for a single function and how to do that without parameters?
In the Kotlin version 1.6.20 there is a new feature called Context receivers. This is a first prototype of context receivers. This feature allows to make functions, properties and classes context-dependent by adding context receivers to their declaration. There is a new syntax for that. In front of the function declaration we can specify a list of contextual types that would be required to invoke this function. A contextual declaration does the following:
It requires all declared context receivers to be present in a caller's scope as implicit receivers.
It brings declared context receivers into the body scope of implicit receivers.
The solution with context receivers looks like the following:
context(CoroutineScope)
fun <T> Iterable<T>.handle() {
map {
launch { /* ... */ }
}
}
someCoroutineScope.launch {
val students = listOf(...)
students.handle()
}
In the context(CoroutineScope) we can declare multiple types, e.g context(CoroutineScope, LogInterface).
Since context receivers feature is a prototype, to enable it add -Xcontext-receivers compiler option in the app's build.gradle file:
apply plugin: 'kotlin-android'
android {
//...
kotlinOptions {
jvmTarget = "11"
freeCompilerArgs += [
"-Xcontext-receivers"
]
}
}
As far as I know, this is currently impossible for types that we don't control. There are plans to add such feature, it is processed under KEEP-259.
I don't know what is the planned roadmap or when we could expect it to be added, but I hope we will see at least some previews this year.
This is a very narrow case, but if your use case is that you have a higher order function where you want code in the lambda to have multiple receivers, and if the types you're wanting to combine are interfaces, you can create a class that wraps the interfaces as delegates. Within the lambda passed to the below function, you can call both Iterable and CoroutineScope functions.
class CoroutineScopeAndIterable<T>(
private val coroutineScope: CoroutineScope,
private val iterable: Iterable<T>
): CoroutineScope by coroutineScope, Iterable<T> by iterable
suspend fun <T> CoroutineScope.runSomething(
iterable: Iterable<T>,
block: suspend CoroutineScopeAndIterable<T>.() -> Unit
) {
CoroutineScopeAndIterable(this, iterable).block()
}
Here is workaround you can use:
val <T> Iterable<T>.handle: CoroutineScope.() -> Unit get() = {
map {
launch { }
}
}

How do I implement navigation between android library modules focusing reusability and separation of concerns?

I'm trying to implement a separate navigation module to navigate between android library modules focusing scalability, reusability and module independence. My application architecture is similar to this example:
My current approach
1- Define NavigatorInterface for each library
2- Implement each NavigatorInterface in NavigationModule. (ofcourse navigation module will know about all other library modules but it does not matter as it won't be reused)
Following is the example code for my above mentioned architecture:
:auth
|-- LoginActivity
|-- SignupActivity
|-- AuthNavigator
public class LoginActivity extends AppCompatActivity {
private NavigatorCoordinator navigator; // how do I achieve this injection, without using Dagger etc.
.....
private void signup(){
navigator.NavigateToSignup(this);
}
private void profile(){
navigator.NavigateToProfile(this);
}
.....
}
public class SignupActivity extends AppCompatActivity {
private NavigatorCoordinator navigator; // how do I achieve this injection, without using Dagger etc.
.....
private void login(){
navigator.NavigateToLogin(this);
}
private void profile(){
navigator.NavigateToProfile(this);
}
.....
}
public interface AuthNavigator {
void NavigateToLogin(Context context);
void NavigateToRegister(Context context);
}
:profile
|-- ProfileActivity
|-- ProfileNavigator
public class ProfileActivity extends AppCompatActivity {
private NavigatorCoordinator navigator; // how do I achieve this injection, without using Dagger etc.
.....
private void about(){
navigator.NavigateToAbout(this);
}
.....
}
public interface ProfileNavigator {
void NavigateToProfile(Context context);
}
:about
|-- AboutActivity
|-- AboutNavigator
public class AboutActivity extends AppCompatActivity {
private NavigatorCoordinator navigator; // how do I achieve this injection, without using Dagger etc.
.....
private void profile(){
navigator.NavigateToProfile(this);
}
.....
}
public interface AboutNavigator {
void NavigateToAbout(Context context);
}
The above approach is a try to eliminate circular dependency within same module :auth as well as between two modules :profile & :about. Below is the implementation of :navigation.
:navigation
|-- Navigator
public class Navigator implements AuthNavigator, ProfileNavigator, AboutNavigator {
#Override
public void NavigateToLogin(Context context) {
context.startActivity(new Intent(context, LoginActivity.class));
}
#Override
public void NavigateToSingup(Context context) {
context.startActivity(new Intent(context, SignupActivity.class));
}
#Override
public void NavigateToProfile(Context context) {
context.startActivity(new Intent(context, ProfileActivity.class));
}
#Override
public void NavigateToAbout(Context context) {
context.startActivity(new Intent(context, AboutActivity.class));
}
}
Questions:
1- I believe, I need to implement the NavigatorCoordinator in :navigation module, but how do I do it? Any example for this implementation?
2- How do I inject the dependency of NavigatorCoordinator in each Activity Class residing in different modules without using Deggar or any other framework? Can we do it by using Application class, if yes please give an example of implementation?
3- How do I setup the Launcher activity from any library, for example: LoginActivity?
4- How do I call the :navigation module from :app module in a way that it starts a specific activity, for example: ProfileActivity?
5- Is this a good inter-module navigation approach to achieve reusability, scalability and separation of concerns?
6- Are there any other similar and good approaches? Any code examples? Link to article?
PS: Please don't tell me to use Navigation Architecture Component.
Actually you can apply some object oriented workaround for this. First of all, you can consider declaring an navigator interface inside common module. Since every module is independent and doesn't know each other, you need to follow a more generic approach. You can define something like a Navigator interface
interface Navigator {
fun navigate(activity: Activity, bundle: Bundle)
}
Now consider opening activities without referencing it, but with a key. You should have a factory to map your given key to an activity navigator. You can think like you want to open AboutActivity with a key, since you don't have its reference. The key can be an integer or maybe class package name, it doesn't matter. I'll stick with package name as a key.
interface NavigatorFactory {
fun of(packageName: String): Navigator
}
And somehow you need to get those navigators when navigating inside an activity. An ideal way is to use application class, because you have its reference in any of your activities. So we defined an interface to apply to application class.
interface NavigatorApplication {
fun getNavigatorFactory(): NavigatorFactory
}
So we end our implementations in common module. Let's continue with app module.
You need to start declaring implementations of these interfaces
class AboutActivityNavigator: Navigator {
public void navigate(Activity activity, bundle: Bundle) {
val intent = Intent(activity, AboutActivity::class.java)
activity.startActivity(intent, bundle)
}
}
Factory implementation to return these Navigator instances.
object NavigatorFactoryImpl: NavigatorFactory {
fun get(key: String): Navigator {
when(key) {
is "com.example.AboutActivity" -> AboutActivityNavigator()
}
}
}
Provide factory with an interface that known by modules. This way modules can reach interface and receive factory instance.
class Application: NavigatorApplication {
val factoryImpl = NavigatorFactoryImpl()
fun getNavigatorFactory() = factoryImpl
}
We are done with app module too. Now you can navigate to every activity you want inside any of your modules.
class ProfileActivity: Activity() {
fun navigateToAboutActivity() {
(application as? NavigatorApplication)?
.getNavigatorFactory()
.of("com.example.AboutActivity")
.navigate(this, Bundle())
}
}
In the end everything comes with a price. Your modules are totally independent. But don't forget, you are still passing some hardcoded key to open an activity. And besides, you have no guarantees for type safety. If you pass something key-value like ("page-id",34) inside a bundle, you may never know if target activity accepts same key and same type.
And if you have a single-activity based architecture in the future, you can also check Jetpack Navigation by Google
You can divide :about into two different sub-modules, one is :implementation and another is :navigate. Put navigationInterface in the :navigate module and import :navigate sub-modules in the :navigation module this way you won't face the problem of circular dependency. Also, import the :navigation module into the :implementation module.

Dagger not injecting Android Annotations class

Alright, I'm having an issue trying to mix frameworks.
So, I have a #SharedPref annotated class that should generate a Shared Preferences manager from Android Annotations. The class looks a bit something like this:
DownloadPrefs.java
#SharedPref(value= SharedPref.Scope.UNIQUE)
public interface DownloadPrefs {
#DefaultBoolean(false)
boolean hasEnabledDownload();
#DefaultBoolean(false)
boolean showedDownloadDialog();
#DefaultLong(0)
long downloadRefreshedOn();
}
Now, I'd like to inject the resulting class (which will be DownloadPrefs_) into a Fragment to make use of it. The fragment has had working injection before adding the new module, so I'm only going to write here what I added:
Fragment.java
#Inject DownloadPrefs_ downloadPrefs;
Now, since the actual DownloadPrefs_ class is generated at runtime, it would make the most sense to create an #Provides annotation for it, since I can't mark a constructor as injected. Nor does the DownloadPrefs_ have a no-arg constructor. The module I'm using then receives the new #Provides:
DownloaderModule.java
#Provides //#Singleton // Does not work with/out #Singleton
DownloadPrefs_ provideDownloadPrefs() {
return new DownloadPrefs_(MinimalBible.getApplication());
}
To be technical about it, the DownloadPrefs_ constructor that gets generated by Android Annotations expects a Context passed to it, I would have guessed that the Application context would be suitable. Otherwise, I'm not sure how I could possibly get access to the Activity context. Or whether that would actually break the ObjectGraph.
However, when I go to run the actual injection, I get the following message:
Caused by: java.lang.IllegalStateException: Errors creating object graph:
org.bspeice.minimalbible.activities.downloader.DownloadPrefs_ has no injectable members. Do you want to add an injectable constructor? required by class org.bspeice.minimalbible.activities.downloader.BookListFragment
Any clue on what's going on? It doesn't seem like the questions asking about "no injectable members" on other SO questions answered my case. I had a working app before adding the code above.
UPDATE: After doing some double-checking, I came across the following weird behavior. If I copy out the pre-built Android Annotations class, rename it, and inject that, everything works. Additionally, I can verify that the original built Android Annotations class (the DownloadPrefs_.java) does in fact exist in the .dex, so Dagger should have no reason to not be able to find it. Everything is doing a debug build, so I can't imagine ProGuard is messing anything up.
At this point, I'm going to create a minimal project to demonstrate the error, and file an issue with Dagger. In the mean time, just need to rewrite the Prefs class until I can get this sorted out.
UPDATE 5/12/2014
Here are the modules responsible for injection:
MinimalBibleModules.java
#Module(
injects = {
MinimalBible.class
},
includes = {
ActivityModules.class
}
)
public class MinimalBibleModules {
}
ActivityModules.java
#Module(
includes = {
ActivityDownloaderModule.class
}
)
public class ActivityModules {
}
ActivityDownloaderModule.java
#Module(
injects = {
BookListFragment.class,
DownloadManager.class,
BookRefreshTask.class
}
)
public class ActivityDownloaderModule {
#Provides #Singleton
DownloadManager provideDownloadManager() {
return new DownloadManager();
}
#Provides
EventBus provideBus() {
return new EventBus();
}
#Provides //#Singleton
DownloadPrefs_ provideDownloadPrefs() {
return new DownloadPrefs_(MinimalBible.getApplication());
}
}
Also, how the graph gets created:
MinimalBible.java
public class MinimalBible extends Application {
private ObjectGraph graph;
private static MinimalBible instance;
public MinimalBible() {
instance = this;
}
#Override
public void onCreate() {
graph = ObjectGraph.create(new MinimalBibleModules());
graph.inject(this);
}
There are two parts here. First, how you get access to the Context. You can do static things as you are, though that's not advisable. Generally, you should configure your graph with a stateful module that carries the context, like this:
#Module
class ApplicationModule {
private final Application application;
public ApplicationModule(Application app) {
this.application = app;
}
// you can mark this singleton, but it's minor overhead
// and the fact that you have a single instance stored
// means it's semantically equivalent. But for clarity
// it's sometimes good to make the point.
#Provides
#Singleton
Application application() {
return application;
}
// optionally: bind it as a Context with a qualifier.
// note: never bind Context without a qualifier annotation
// as Activity and Application are both Context subtypes.
#Provides
#Singleton
#PerApplication
Context appContext(Application app) {
// Doing this instead of returning this.application is
// semantically equivalent but links #PerApplication Context
// to Application, so in graph analysis and error reporting
// the link is clearer. That's a personal choice.
return app;
}
}
At any rate, you then when you create the graph:
Application appInstance = ...;
ObjectGraph appGraph = ObjectGraph.create(
MyAppModule.class,
new ApplicationModule(appInstance));
The Application is then seeded into the graph and can be depended-upon by other types that declare it as a dependency.

Categories

Resources