3

Consider an Activity MainActivity with a fragment MainFragment. The fragment has some complex layout hierarchy and a view group Frame which comes from a library com.framer:frame_me:1.1.

If I have 2 flavours foo and bar, and I want this Frame to be there only in bar flavour and not in foo, the XML element java code and dependency. How should I do this?

I can compile the dependency using

barCompile 'com.framer:frame_me:1.1'

But what about the fragment and its XML. I don't want to write 2 variations the fragment in both flavours because I don't want to maintain the same code in 2 places.


One possible idea (probably a bad one) in my mind is that this:

  1. Move the XML element in a separate file in bar source set. Add ViewStub element in the foo source set with the same name. Now include this XML file using include in the fragment XML
  2. Add an interface to handle Frame view in main source set. Add an empty implementation in foo source set and one in bar source set. This way all logic can remain in bar while all common logic remains in main source set.

This all sounds an awfully lot of work just to write flavour specific code and xml.

Sourabh
  • 7,371
  • 9
  • 45
  • 86
  • why don't you just extend a View for your Frame, have this view in your main.xml but GONE and enable it only if you are running on flavor? You can have an extra xml that is inflated in the new subclass of View – Klitos G. Mar 18 '17 at 19:08
  • `barCompile`! The `Frame` doesn't even exist in `foo` flavour, which will throw an error. Or did I not understand your comment properly? – Sourabh Mar 18 '17 at 19:10
  • will your xml need the dependency too? – Klitos G. Mar 18 '17 at 19:14
  • Yeah, the `Frame` class exists in dependency. I can't add a view in xml which doesn't exist. – Sourabh Mar 18 '17 at 19:16
  • You could use a container ViewGroup subclass for your Frame in your main xml. But it still tends to go to your proposed solution. You will have to implement this ViewGroup subclass twice, once for every flavor. – Klitos G. Mar 18 '17 at 19:20
  • Check [this answer](http://stackoverflow.com/questions/24119557/android-using-gradle-build-flavors-in-the-code-like-an-if-case).Use `BuildConfig.FLAVOR` and manage visibility of `Frame` according to flavor. – Pravin Divraniya Mar 22 '17 at 06:27

3 Answers3

1

How about replacing the Frame tag in your XML with a FrameLayout container?

Then in the bar flavor's source code you can instantiate the Frame and say container.addView(frame). While the foo flavor will have no reference to the Frame class and will ignore the container.

This is similar to your first approach, but without having to maintain separate resource sets. And it seems reasonable, that you will have some flavor-specific java code anyway.

dev.bmax
  • 7,278
  • 2
  • 27
  • 38
  • But how do I keep that flavour specific Java code separate without duplicating the whole Fragment in 2 flavours? – Sourabh Mar 21 '17 at 14:45
  • You could move part of the code to a helper class, copy that file to the second flavor and modify it there. But you have to have a whole source file in both flavors. – dev.bmax Mar 21 '17 at 15:24
  • So essentially the same thing I proposed, but just a bit different. I guess that's the only way. Sad. – Sourabh Mar 22 '17 at 13:07
  • This doesn't seem overwhelming. You can minimize the effort by making a really small helper class, that contains only the flavor-specific code. – dev.bmax Mar 22 '17 at 14:03
  • yeah but that gets more and more complex as the things different in 2 flavours get more and more different but still sharing a lot of common code. I fixed my code minutes back and moved all common code to a base class and extended 2 classes in 2 flavours, leaving one completely empty. This looks like it'll work well in future too. I'll wait a couple of days before accepting this answer, in case a better one shows up – Sourabh Mar 22 '17 at 14:07
0

what's about build.gradle sourceSets option ? You could put Fragment and XML in bar folders and then set:

android {
    productFlavors {
         ...
    }
    sourceSets {
         bar.java.srcDirs = ['src/bar/java']
         bar.res.srcDirs = ['src/bar/res']
    }
}
alrama
  • 619
  • 11
  • 17
  • 1
    I know about source set, my question is how can I do this without duplicating a lot of code in 2 flavor java source sets. Because I don't like code duplication – Sourabh Mar 21 '17 at 14:47
0

You just need abstraction. Since resources are identified using an integer index into the R class, you can use int variables as placeholders for the layout files, and given the fact a layout element ID is searched within the active layout, you can recycle the common elements. First, create a common fragment class, with all the common elements:

public abstract class BaseFlavorFragment extends Fragment {

/*Define an interface for whatever code the fragment may need from the outside and a member for keeping reference of that. You can also use the host activity, this is just for flexibility*/
    public interface whateverThisDoes{
        void do();
    }
/*All the common fragment members go here, as protected so you can reach them from every subclass*/
    protected TextView title;
    protected Button mainButton;
    protected whateverThisDoes listener;

    public void setWhateverThisDoes(whateverThisDoes listener){
        this.listener = listener;
    }
/*Finally, create a int variable that will hold the reference to the layout file you need to use. you will set this in every flavor using the setContainer method.*/
    protected int layout = 0;

    /*this will allow you to select which XML to use
layout = R.layout.flavorlayout*/
    public abstract setContainer();

    /*Use this method to inflate any flavor members, like the Frame you mentioned*/
    public abstract void inflateComponents();
    /*Use this to set listeners, data, or anything the flavor controls do*/
    public abstract void setBehaviors();

    /*Set here anything the common controls do*/
    protected void setCommonBehaviors(){
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //whatever                
            }
        });
        setBehaviors();
    }

    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContainer();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
       super.onCreateView(inflater, container, savedInstanceState);
       View view = inflater.inflate(layout, container, false);
       /*Inflate common components*/
       title =  (TextView) root.findViewById(R.id.title);
       button =  (Button) root.findViewById(R.id.button);
       /*inflate flavor components, if there's any*/
       inflateComponents(); 
       /*assign data, listeners, whatever the flavor controls do*/
       setBehaviors();          
       return view;
    }
}

Now, you can just create an implementation for Foo and Bar. if the only difference is the layout file, put everything into the base class, and set the layout file using setContainer(). If you have more differences you just need to deal with them into each abstract method. The base class can live into the the common code, the implementations, into each flavor. If you don't need to set any behavioral code from the outside, you can drop the interface.

Fco P.
  • 2,018
  • 13
  • 16
  • I did something similar, not with this many abstract methods but simply overrode some methods to add functionality and added views dynamically. – Sourabh Mar 26 '17 at 20:51
  • How would the system know what code to run for a specific flavor? I assume, you load the specific fragment implementation after checking the current flavor? – eimmer Dec 30 '19 at 09:10