0

I'm developing an android library (.aar) and I was wondering if it was possible to, as the title suggest, force a user to implement an interface or extend an abstract class of my library.

I already know that I could just go with a class like this in my library :

public class MyLibrary
{
    public interface VariablesInterface
    {
        void createVariables();
    }

    private static VariablesInterface vi = null;

    public void setVariablesInterface(VariablesInterface v)
    {
        vi = v;
    }

    private static void SomeWork()
    {
        if (vi == null)
        {
            throw new RuntimeException("You noob.");
        }
        else
        {
            // do work
        }
    }
}

The library will work "alone" at some point, and when it will come to SomeWork(), if the interface isn't implemented it will crash, but this could only be seen at runtime.

Is there a way to have this behaviour when compiling the user's application ?

The goal is to avoid the user forgetting that he have to implement this without having to write it in the documentation and hope the user will probably read it.

Thanks for reading !


EDIT

I think that this question need some enhancement and background. The purpose of the library is to provide classes that create variables which manages preferences, e.g. :

public class VarPreferenceBoolean extends VarPreference
{
    private boolean defaultValue;

    public VarPreferenceBoolean(String key, boolean defaultValue)
    {
        super(key, true);
        this.defaultValue = defaultValue;
    }

    public void setValue(Context context, boolean value)
    {
        SharedPreferences.Editor e = context.getSharedPreferences(PropertiesManager.preferenceFileName, Context.MODE_PRIVATE).edit();
        e.putBoolean(key, value);
        e.commit();
    }

    public boolean getValue(Context context)
    {
        readPropFile(context);
        SharedPreferences sp = context.getSharedPreferences(PropertiesManager.preferenceFileName, Context.MODE_PRIVATE);
        return sp.getBoolean(key, defaultValue);
    }
}

The same goes for int, string and so on. In the super class, I add each VarPreference to a List to keep the library acknowledged of all the variables availables. Note the readPropFile inside the getter.

Then, the user use the library in his project like this :

public class Constants
{
    public static final VarPreferenceInt     FILETYPE;
    public static final VarPreferenceInt     DATAMODE;
    public static final VarPreferenceString  URL_ONLINE;
    public static final VarPreferenceBoolean UPDATING;
    public static final VarPreferenceLong    LAST_UPDATE;

    static
    {
        FILETYPE = new VarPreferenceInt("FileType", MyFile.FileType.LOCAL.getValue());
        DATAMODE = new VarPreferenceInt("DataMode", DataProvider.DataMode.OFFLINE.getValue());
        URL_ONLINE = new VarPreferenceString("UrlOnline", "http://pouetpouet.fr");
        UPDATING = new VarPreferenceBoolean("Updating", false);
        LAST_UPDATE = new VarPreferenceLong("LastUpdate", 0L);
    }
}

Now, when the user call an accessor, readPropFile will first search if a .properties file exist and modify accordingly the preferences if it found matches between the list of VarPreference and the properties of the file. Then it will delete the file and the accessor will return the value.

This is what exists today.

Now we want another application (let's say Pilot) to be able to get the VarPreferences of the user's application (let's say Client). Both implements the library.

Pilot send an Intent asking for the VarPreference list of Client, putting in extra the package name of Client. The library receive the intent, verify the packagename, if it's Client it send back the list.

Problem is, if Client hasn't started, no VarPreference exists, and the list is empty.

I need to force the user to create his VarPreference in an method that my library know, to be able to call it whenever I want, and create the VarPreferences of the user when it's necessary.

Hope this is clearer !


EDIT

I rethought about all of this with a colleague and it just hit us that all this stack is biaised.

I didn't explain well and even if I said it, I didn't take account enough of this : everything needs to be done from the library. So, even if I give an interface to the library, the application will have to run and call this affectation first in order to let the library work alone.

We are heading towards introspection now. (This is the goal, it may not be possible...) There will be an abstract class inside the library, with an abstract method where the user will place all of the VarPreferences creations. The user will have to extends this class and call the method in order to create his VarPreferences. In the library, a method will search by introspection a child of the abstract class, create an instance of this child and call the method that will create the VarPreferences.

  • 7
    If you make `SomeWork` take a `VariablesInterface` argument instead of storing it in a static field, that'll force the user to implement it =) – Louis Wasserman Mar 13 '17 at 16:42
  • 3
    Moreover, your existing code will *prevent* the developer from giving you a `VariablesInterface` instance, as `vi` is `private`. – CommonsWare Mar 13 '17 at 16:44
  • @LouisWasserman Yes, but the point is the user will never call directly SomeWork, hence he won't be able to give an instance of VariablesInterface. – Quentin Beuvelet Mar 14 '17 at 08:22
  • @CommonsWare Indeed, it was a typo in the example :/ – Quentin Beuvelet Mar 14 '17 at 08:23
  • Then whatever point the user *does* call should accept a SomeInterface argument. – Louis Wasserman Mar 14 '17 at 16:31
  • It is common to make the constructor private so you can control the creation of `MyLibrary` from a `static` method which can do the introspection. Not sure if that is an improvement on your inner abstract class. – Gray Mar 15 '17 at 13:04

2 Answers2

0

I would leave the abstract classes and interfaces in the main library and load the rest of your code via classloader from another. JDBC works like this.

  • I'm not sure that I understand your solution, could you explain a bit more ? – Quentin Beuvelet Mar 14 '17 at 08:30
  • The way I understand the sentence: "I need to force the user to create his VarPreference in an method that my library know" is, that you will have an abstract class or interface which the client must implement and you don't want him to implement/override anything else. Am I right? – Matthias Danetzky Mar 15 '17 at 06:03
  • As I understand the edit - you want to analyse the code produced by the user of your library using introspection (https://coderanch.com/t/521121/java/Java-Reflection-Java-Introspection). What I still don't understand is - what do you mean by 'force the user to implement interface/...'? – Matthias Danetzky Mar 15 '17 at 11:18
  • Dang it, I always forgot reflection and mix introspection and reflection when I talk... Anyway, the ideal way of what I want is like, the moment you import the library with gradle, gradle will yell at you that you have to implement something of the library, but that's not possible... – Quentin Beuvelet Mar 15 '17 at 14:58
0

Is there a way to have this behaviour when compiling the user's application ?

I see no way to force a compilation failure. However, if you force them to supply a VariablesInterface in the constructor then it will fail immediately. Make the VariablesInterface be final and only initialize it in the constructor:

 public class MyLibrary {
     private final VariablesInterface vi;

     public MyLibrary(VariablesInterface vi) {
        if (vi == null) {
            throw new IllegalArgumentException("vi can't be null");
        }
        this.vi = vi;
     }
     ...

If you can't change the constructor then you can also add to any SomeWork public methods some sort of configuration check method to make sure the the vi wiring has properly been done but this requires careful programming to make sure all public methods are covered.

public void somePublicMethod() {
    checkWiring();
    ...
}

private void checkWiring() {
    if (vi == null) {
        throw new IllegalStateException("vi needs to be specified");
    }
}
Gray
  • 115,027
  • 24
  • 293
  • 354
  • I may be missing something but I don't really see how this will prevent the code from compiling. – Quentin Beuvelet Mar 14 '17 at 08:38
  • Good point. I've tweaked my answer. However there is no way for them to instantiate your library without specifying a `VariablesInterface` so they can't use it. – Gray Mar 14 '17 at 19:42