gaelmarhic / Quadrant

A Gradle plugin for Android that makes navigation easy in multi-module projects.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Support package namespace when generating QuadrantConstants

marcellogalhardo opened this issue · comments

Today when generating QuadrantConstants, Quadrant remove the namespace and create them using only the Activity name. For example: com.gaelmarhic.quadrant.ModuleOneFirstActivity -> MODULE_ONE_SECOND_ACTIVITY. Therefore, 2 activities living in different packages (in same modules or different modules) with same name would conflict.

As an alternative, we can keep the package namespace to reduce the chance of conflicts. For example, the previous example could be done in the following way: com.gaelmarhic.quadrant.ModuleOneFirstActivity -> com_gaelmarhic_quadrant_ModuleTwoSecondActivity. As a minor advantage, Quadrant explicitly tells the developers the package of the Activity to avoid them entering the constant class.

As this is a breaking change, Quadrant version should be increased, and we can keep a flag to allow temporary backward compatibility (packageNamespaceEnabled: Boolean = true).

Open questions:

  • What would be the format of those package namespace names?
  • Should we keep the backward compatibility or not?

Hey there @marcellogalhardo.

First of all, thank you for your interest in Quadrant and for raising this issue.
Also, please, accept my apologies for taking this long in replying.

The issue you are commenting is quite interesting and actually came through my mind when I originally wrote Quadrant.
I thought about tackling it at some point, but I decided not to, for the following reasons:

  • I could not personally think of any legitimate case where we would want to have two Activities with the same name. IMHO, different Activities, unless they are being refactored (and therefore have a copy), should have different names.
  • In Quadrant, there already is a config, called perModule, which will generate a file of Activity name constants per Gradle module, instead of the original QuadrantConstants file, which I believe would solve the issue having two Activities with the same name in different modules. This would leave us still with the issue that two Activities, inside the same Gradle module, could have the same name. However, as commented in the first point, I personally didn't think of any valid case for this, which is why I decided not to support this feature.

As commented, this was my original thinking, and I find it interesting that you are bringing it back on the table. You probably ran into some edge cases I did not think of, and I'd be happy to hear about them.

Regarding your open questions:

  • What would be the format of those package namespace names? Good question! I definitely think that prepending the entire package would make the constant name too verbose (Example:COM_GAELMARHIC_QUADRANT_MODULE_ONE_FIRST_ACTIVITY). What if we have a deeply nested package structure?
    As an example, an idea could possibly be to remove the common part of that constant. For example, if we have com.gaelmarhic.quadrant.package1.ModuleOneFirstActivity and com.gaelmarhic.quadrant.package2.ModuleOneFirstActivity, we could generate the following constants: PACKAGE1_MODULE_ONE_FIRST_ACTIVITY and PACKAGE2_MODULE_ONE_FIRST_ACTIVITY.

  • Should we keep the backward compatibility or not? Another good question. Personally, my first idea on this one would actually be to set packageNamespaceEnabled as false by default. As commented above, this would be a breaking change which I believe is an edge case.

Finally, there is also another solution that could be taken into account, which would consist in using a meta-data in the Manifest, in order to handle such cases (similarly to the addressability or ignore mechanisms already used in Quadrant).
For example, we could have:

<activity android:name="com.gaelmarhic.quadrant.MainActivity">

    <meta-data
        android:name="namespace"
        android:value="NAMESPACE" />

</activity>

which would generate a constant called NAMESPACE_MAIN_ACTIVITY.

@marcellogalhardo Thanks again, and let me know what you think :)

Hey @gaelmarhic - thank you for the detailed reply.

I could not personally think of any legitimate case where we would want to have two Activities with the same name.

Most cases, we won't have two Activities with same name. But in a huge project where you have teams contributing on different features (that can't be always communicating, yep), that may not hold true: you can end up with activities with same name living in different modules (e.g., both checkout and restaurant could have an DetailActivity in different packages).

It is important to note that, for Android/Java this is a valid scenario.

In Quadrant, there already is a config, called perModule, which will generate a file of Activity name constants per Gradle module, instead of the original QuadrantConstants file, which I believe would solve the issue having two Activities with the same name in different modules.

That is very limited. A module is a compilation unit but it does not define a qualified name, that is made with packages. For instance:

  1. A module can have two activities with same name as much as their qualified names are different (packages).
  2. Two modules can have the same name, if they have a different parent structure (e.g., :checkout:core and :restaurant:core would create two modules named core).

It is important to note that, for Android/Java those are a valid scenario.

What would be the format of those package namespace names?

My understand is that Quadrant mission is to generates a type-safe way to access a qualified name. With that in mind, ideally it should generate the qualified name itself: com_gaelmarhic_quadrant_package1_ModuleOneFirstActivity. That already fixes all the problems: if it is a valid qualified name, quadrant will know what to do. If it is not, it will fail.

If we decide to generate something like PACKAGE1_MODULE_ONE_FIRST_ACTIVITY we are not respecting some rules from Java/Kotlin, as such:

  1. As we made it upper-case, the names are now not case sensitive.
  2. As we are reducing the size of the constant name, we are also disrespecting how a qualified name is.

I don't think it is worth to break the rules for qualified name in favor of a smaller string. Ideally, we should offer a type-safe accessor that is compatible with Java's qualified name - it is responsibility of the user to create aliases for the names if needed.

Should we keep the backward compatibility or not?

I did recommend to keep it as true as it guarantee a better behaviour if compared to Java/Kotlin qualified names. That said, I have no issue in having it as default to false and simple mentioning it on the README file.

Finally, there is also another solution that could be taken into account, which would consist in using a meta-data

I would personally not do it as the new behaviour would guarantee qualified names are always valid - and cover all use cases Java covers. If it works with Java, it will work with Quadrant. Aliases can still be created manually in code.

That said, I don't have a strong position.

To conclude, my understand is that the new behaviour is safer by default, it will guarantee Java's qualified name is respected, and reduce the corner cases we might run without affecting the library functionality.

Let me know what do you think. 😄

Hey @marcellogalhardo!

Thank you for this great explanation. You actually convinced me that your proposal could be really beneficial!

I still have a couple of open questions, which are not blockers, but just general doubts:

  • One of the current advantages of Quadrant is that if you move an Activity around, without renaming it, you won't have to update any usages since the name of the constant is not going to change.
    With your proposal, the constant name will be updated every time in that case, which could be painful in case you have many usages. Could you detail a bit more what you meant by creating aliases?

  • What is the format you suggest exactly?
    If I understand properly, it would be the packages separated by underscores, and the class name as it is declared, right?

  • packageNamespaceEnabled explicitly tells that we are gonna use the package namespace, but how about the fact that we are changing the case too? Is it included in the package namespace concept?

Finally, I'd insist on keeping it as false by default for now. I think it is nice to enable this feature already, but I'd like to have no breaking change at this point.

Let's go on with it. I will mention a few things on the PR.

Thanks again @marcellogalhardo ! :)