6

I am using this library which I have installed locally as a module. I'm able to access it via my main project, but I'm not able to do the opposite. For example, access a variable in my main project from this library...

I tried adding this line in the library's build.gradle:

    implementation project(':app')

But I get this weird error:

Circular dependency between the following tasks:
:placepicker:generateDebugRFile
\--- :placepicker:generateDebugRFile (*)
    
(*) - details omitted (listed previously)

How can I fix this? My project is in Java and my library is in Kotlin

Bandy
  • 161
  • 12

2 Answers2

3

"Circular dependency" can be fixed only by removing dependency that causes this issue on one of two sides.

If you need to access some data from the library code you can implement an interface in a library that will be extended by some class in your project. Then you will be able to use extended class in your library and access methods defined in the interface.

Example

Let's imagine you need to get a reference to the application context within your library. You should create an interface:

interface ContextAccessor {
    // Marking it as optional just in case
    // you will not be able to get a context
    // from an object that implemented ContextAccessor
    fun getApplicationContext(): Application?
}

Because you added the library as a dependency in your project you have access to ContextAccessor. Extend some class with this interface and implement the getApplicationContext method. Let's say you want to extend some Activity.

class MyActivity: Activity, ContextAccessor {
    ... other code here

    override fun getApplicationContext(): Application? = application
}

Now from within your MyActivity class, you can set ContextAccessor into your library as if it was dependency injection.

class MyActivity: Activity, ContextAccessor {
    ... other code here 
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val someLibraryClassInstance = SomeLibraryClass()
        someLibraryClassInstance.setContextAccessor(this)
        // OR -> `someLibraryClassInstance.contextAccessor = this`
    }
}

WARNING: when you save a reference to any Android component, especially Activity, Fragment, Dialog etc., make sure you later remove this reference when the object is going to be destroyed to avoid memory leaks.

An example of how to remove a reference on a little bit modified code from the previous code snippet:

class MyActivity: Activity, ContextAccessor {
    ... other code here 

    private val someLibraryClassInstance = SomeLibraryClass()   
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
         
        // ContextAccessor reference is set to some library class
        someLibraryClassInstance.setContextAccessor(this)
    }

    override fun onDestroy() {
        super.onDestroy()

        // Super important!
        someLibraryClassInstance.setContextAccessor(null)
        // OR create some method like `someLibraryClassInstance.removeContextAccessor(this)`
    }
}

Same classes in Java

interface ContextAccessor {
    // Marking it as optional just in case
    // you will not be able to get a context
    // from an object that implemented ContextAccessor
    Application getApplicationContext();
}
public class MyActivity extends Activity implements  MyActivity.ContextAccessor {
    
    private SomeLibraryClass someLibraryClassInstance = SomeLibraryClass();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // ContextAccessor reference is set to some library class
        someLibraryClassInstance.setContextAccessor(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // Super important!
        someLibraryClassInstance.setContextAccessor(null);
        // OR create some method like `someLibraryClassInstance.removeContextAccessor(this)`
    }

    @Override
    public Application getApplicationContext() {
        return super.getApplication();
    }
}

Update (10 aug 2020): how to use ContextAccessor?

Here is how you can use ContextAccessor in your library:

class SomeLibraryClass {
    private var mContextAccessor: ContextAccessor?

    fun setContextAccessor(contextAccessor: ContextAccessor?) {
        mContextAccessor = contextAccessor
    }
    
    fun someOtherMethod() {
        mContextAccessor?.getAppContext()?.let { nonNullContext ->
            // use nonNullContext here
        }
    }
}
Jenea Vranceanu
  • 4,530
  • 2
  • 18
  • 34
  • My project activity is in Java and my library is in Kotlin, so please edit your code – Bandy Aug 06 '20 at 21:10
  • @Bandy, here you go. Remember `interface ContextAccessor` is defined in the library so it can be used by other projects that depend on the library! – Jenea Vranceanu Aug 07 '20 at 07:24
  • Not a big deal. Rename getApplicationContext() function defined in the interface to something else, e.g.: getAppContext(). And if you have implemented it somewhere rename there too. – Jenea Vranceanu Aug 08 '20 at 18:36
  • My mistake. In java interfaces should be enumerated after the word “implements” when they are being implemented. Answer updated. – Jenea Vranceanu Aug 08 '20 at 19:30
  • Implement the interface in your activity. Follow my examples. ‘@Override public Application getAppContext() { return super.getApplication(); }’ – Jenea Vranceanu Aug 08 '20 at 20:01
  • okay yes sorry i uploaded wring screenshot. here is the correct one : https://i.imgur.com/HgSJHMT.png – Bandy Aug 08 '20 at 20:04
  • In a class on which you call *setContextAccessor* you must implement this method. Create private variable *mContextAccessor* and create a setter for it called *setContextAccessor*. Its type must be *ContextAccessor*. Just a simple variable with a setter method. Don’t forget to use it. – Jenea Vranceanu Aug 09 '20 at 05:20
  • can you please add an example of that in your answer? – Bandy Aug 09 '20 at 16:57
  • I think there is enough information to fix your issue. Create a private variable and a setter for it: [java encapsulation](https://www.w3schools.com/java/java_encapsulation.asp). – Jenea Vranceanu Aug 09 '20 at 20:35
  • I tried my best but I failed (i get errors), see here: https://i.imgur.com/oXHNeXM.png I'd appreciate it if you add an example in kotlin in your answer so that I award you the bounty if it works out – Bandy Aug 09 '20 at 21:49
  • ok. now i set up everything. can you show me an example of how to access a variable in a project class from a library class? – Bandy Aug 10 '20 at 10:37
  • my project class is in java and my library class is in kotlin. – Bandy Aug 10 '20 at 10:46
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/219540/discussion-between-jenea-vranceanu-and-bandy). – Jenea Vranceanu Aug 10 '20 at 10:49
2

You can move your classes that used in both modules to another (third module) and use it in your app module and another module you want.

in app module: implementation project(":third")

in second module: implementation project(":third")

Axbor Axrorov
  • 2,720
  • 2
  • 17
  • 35