Pages

Showing posts with label Java. Show all posts
Showing posts with label Java. Show all posts

Saturday, January 17, 2015

Communication across the UI

One of the greater things in newer Android versions is the Fragments. It allows you to split the UI into smaller pieces that can be put together in different ways, depending on different circumstances like screen size and such. However some times you might need to adapt things in one Fragment based on some conditions in another. Talking to an Activity from within a Fragment is easy enough, but Fragments to Fragments is another thing. Especially since it is unadvised to have one specific Fragment talk to another specific one.

A better solution is to have a small message delivery system that works similar to Android's broadcast system. That way you can parse information between Fragments, but without targeting specific ones. For this we need an extended Activity that has these capabilities.

One issue with Java (or not, depending on who you ask) is that Java does not support multi-inheritance. I for one is quite fine with this, but I would like to see something like traits be introduced into the language. In this the problem lies with the many different Activity and Fragment classes that Android has available. We don't want to copy paste everything into each Activity and Fragment sub-class that we want to use.

With the lack of traits or similar, we will be using the famous logic trick instead where we place all the logic into one class, and then only create redirection in each of the Activity and Fragment classes. Not only will this limit the size of each Activity and Fragment class, but it will also make sure that we only have to edit one class if we want to change something.

ActivityLogic.java:
final public class ActivityLogic {
    public static interface IActivityLogic {
        public void onReceiveMessage(String message, Object data, Boolean sticky);
        public void onFragmentAttachment(IActivityLogicFragment fragment);
        public void onFragmentDetachment(IActivityLogicFragment fragment);
        public void sendMessage(String message, Object data);
        public void sendMessage(String message, Object data, Boolean sticky);
    }
    
    public static interface IActivityLogicFragment {
        public void onReceiveMessage(String message, Object data, Boolean sticky);
    }
    
    private final static class ActivityLogic_MessageHandler extends AbstractHandler<ActivityLogic> {
        public ActivityLogic_MessageHandler(ActivityLogic reference) {
            super(reference);
        }

        @Override
        public void handleMessage(Message msg) {
            ActivityLogic logic = getReference();
            
            if (logic != null) {
                IActivityLogic activity = logic.ActivityLogic_mActivity.get();
                
                if (activity != null) {
                    Set<IActivityLogicFragment> fragments = new HashSet<IActivityLogicFragment>(logic.ActivityLogic_mFragments);
                    Object[] input = (Object[]) msg.obj;
                    String message = (String) input[0];
                    Object data = input[1];
                    
                    activity.onReceiveMessage(message, data, false);
                    
                    for (IActivityLogicFragment fragment : fragments) {
                        fragment.onReceiveMessage(message, data, false);
                    }
                }
            }
        }
    }
    
    private Set<IActivityLogicFragment> ActivityLogic_mFragments = Collections.newSetFromMap(new WeakHashMap<IActivityLogicFragment, Boolean>());
    private Map<String, Object> ActivityLogic_mStickyMessages = new HashMap<String, Object>();
    
    private ActivityLogic_MessageHandler ActivityLogic_mMessageHandler;
    private WeakReference<IActivityLogic> ActivityLogic_mActivity;
    
    public ActivityLogic(IActivityLogic activity) {
        ActivityLogic_mActivity = new WeakReference<IActivityLogic>(activity);
        ActivityLogic_mMessageHandler = new ActivityLogic_MessageHandler(this);
    }
    
    public void onFragmentAttachment(IActivityLogicFragment fragment) {
        synchronized (ActivityLogic_mFragments) {
            ActivityLogic_mFragments.add(fragment);
            
            for (String message : ActivityLogic_mStickyMessages.keySet()) {
                fragment.onReceiveMessage(message, ActivityLogic_mStickyMessages.get(message), true);
            }
        }
    }
    
    public void onFragmentDetachment(IActivityLogicFragment fragment) {
        synchronized (ActivityLogic_mFragments) {
            ActivityLogic_mFragments.remove(fragment);
        }
    }
    
    public void sendMessage(String message, Object data) {
        sendMessage(message, data, false);
    }
    
    public void sendMessage(String message, Object data, Boolean sticky) {
        synchronized(ActivityLogic_mFragments) {
            if (sticky) {
                ActivityLogic_mStickyMessages.put(message, data);
            }

            ActivityLogic_mMessageHandler.obtainMessage(0, new Object[]{message, data}).sendToTarget();
        }
    }
}

The above Activity Logic class is only the first step towards getting our message delivery system working. Since this is meant to be used to communicate between Fragments, we still need a Fragment Logic class as well.

FragmentLogic.java:
final public class FragmentLogic {
    
    public static interface IFragmentLogic extends IActivityLogicFragment {
        public IActivityLogic getParent();
        public void sendMessage(String message, Object data);
        public void sendMessage(String message, Object data, Boolean sticky);
    }
    
    private WeakReference<IActivityLogic> FragmentLogic_mActivity;
    private WeakReference<IFragmentLogic> FragmentLogic_mFragment;
    
    public FragmentLogic(IFragmentLogic fragment) {
        FragmentLogic_mFragment = new WeakReference<IFragmentLogic>(fragment);
    }
    
    public void onAttach(IActivityLogic activity) {
        FragmentLogic_mActivity = new WeakReference<IActivityLogic>(activity);

        IFragmentLogic fragment = FragmentLogic_mFragment.get();
        if (fragment != null) {
            activity.onFragmentAttachment(fragment);
        }
    }
    
    public void onDetach() {
        IFragmentLogic fragment = FragmentLogic_mFragment.get();
        IActivityLogic activity = FragmentLogic_mActivity.get();
        
        if (fragment != null && activity != null) {
            activity.onFragmentDetachment(fragment);
        }
        
        FragmentLogic_mActivity.clear();
    }
    
    public IActivityLogic getParent() {        
        return FragmentLogic_mActivity.get();
    }
    
    public void sendMessage(String message, Object data) {
        sendMessage(message, data, false);
    }
    
    public void sendMessage(String message, Object data, Boolean sticky) {
        IActivityLogic activity = getParent();
        
        if (activity != null) {
            activity.sendMessage(message, data, sticky);
        }
    }
}

Now that we have all of the logic behind this delivery system, we need to extend some classes that will be using these classes. Let's start with the Activity class.

AbstractActivity.java:
public abstract class AbstractActivity extends Activity implements IActivityLogic {
    
    private ActivityLogic mLogic;
    
    public AbstractActivity() {
        mLogic = new ActivityLogic(this);
    }
    
    @Override
    public void onFragmentAttachment(IActivityLogicFragment fragment) {
        mLogic.onFragmentAttachment(fragment);
    }
    
    @Override
    public void onFragmentDetachment(IActivityLogicFragment fragment) {
        mLogic.onFragmentDetachment(fragment);
    }
    
    @Override
    public void onReceiveMessage(String message, Object data, Boolean sticky) {}
    
    @Override
    public final void sendMessage(String message, Object data) {
        mLogic.sendMessage(message, data);
    }
    
    @Override
    public final void sendMessage(String message, Object data, Boolean sticky) {
        mLogic.sendMessage(message, data, sticky);
    }
}

And we will of course also need a Fragment class.

AbstractFragment.java:
public abstract class AbstractFragment extends Fragment implements IFragmentLogic {
    
    private FragmentLogic mLogic;
    
    public AbstractFragment() {
        mLogic = new FragmentLogic(this);
    }

    @Override
    public final IActivityLogic getParent() {        
        return mLogic.getParent();
    }
    
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        
        mLogic.onAttach((IActivityLogic) activity);
    }
    
    @Override
    public void onDetach() {
        super.onDetach();
        
        mLogic.onDetach();
    }
    
    public void onReceiveMessage(String message, Object data, Boolean sticky) {}
    
    public final void sendMessage(String message, Object data) {
        mLogic.sendMessage(message, data);
    }
    
    public final void sendMessage(String message, Object data, Boolean sticky) {
        mLogic.sendMessage(message, data, sticky);
    }
}

And that's it. We now have one normal Activity and Fragment class that we can use. Note that these are abstract, this is because we use these to extend our actual ones.

Let's take a small example of how to use these.

SomeFragment.java:
public class SomeFragment extends AbstractFragment {
    @Override
    public void onResume() {
        // Send a message to other Fragments or the Activity
        if (someVariable == "someCondition") {
            sendMessage("tellSomeone", "someCondition");
        }
    }

    @Override
    public void onReceiveMessage(String message, Object data, Boolean sticky) {
        // Receive message from other Fragments or the Activity
        if (message.equals("someOtherCondition")) {
            // Do something
        }
    }
}

If we need other types as well, like DialogFragment or ActionBarActivity, we can re-use our logic classes to easily create them.

Friday, August 23, 2013

Controlled Environment

You are building a library of some sort, and you want to have a primary class with additional "extender" classes which all should be accessed through your primary one. Why? Don't know, hopefully you do since your the one making it. However, what is to stop anyone from accessing your "extender" classes directly? You could add your "extender" classes as sub-classes to the primary and make your constructors private or protected. But if your planing on expanding this library a great deal, you will end up with a pretty large file at the end. And splitting your "extender" classes into single files with private and protected constructors will just ensure that not even your primary class will be able to create instances of them. So what's left? I know that some languages actually has a feature which allows you to lock a class and define whom has access to create an instance. I have not worked with Java for long, so I don't know whether or not this feature exists in Java. But there are other ways of doing this, which will actually work in other OOP capable languages as well.

The trick here is Data Types. This is something that Java is very strict about, which makes this so much easier. What you need to do, is have a small class that only your primary can create an instance from. The easiest way is to place this as a sub-class to the primary class. This small class will be used to parse along as an argument to a static method in each "extender" class, who’s job is to return an instance of themselves. Meanwhile we will keep each "extender" constructor private, so that no other can access it. And we will make each class final, so that they cannot be extended in order to bypass this protection.

This is our primary class:
public final class MyPrimary {
    public MyExtender extender(String param) {
        MyTransport transport = new MyTransport();
        transport.arguments = new Object[] { (Object) param };

        return (MyExtender) MyExtender.getInstance( transport ).instance;
    }

    public final static interface MyDataType {}

    public final static class MyTransport {
        private MyTransport() {}

        Object[] arguments;
        MyDataType instance;
    }
}

This is one of our extenders:
public final class MyExtender implements MyDataType {
    public static MyTransport getInstance(MyTransport transport) {
        transport.instance = (MyDataType) new MyExtender( (String) transport.arguments[0] );

        return transport;
    }

    private MyExtender(String param) {
        ...
    }
}

In order to get an instance from MyExtender, you will need to parse through it's getInstance method. However, this method requires a MyTransport argument, which only the primary class can create. From the getInstance method, it will attach an instance of itself and then return the MyTransport object to the primary class. And by implementing MyDataType, you can use the same MyTransport class for any extender classes as they will all be able to be casted to the same data type.

That is all that there is to it. And like mentioned above, this can be used in any OOP language. In some however, you will need some manual checking of data types. PHP for an example does not really care about data types, so you will need to do a manual instanceOf check in the extender getInstance methods to make sure that the argument really is a MyTransport object. But that is not all that difficult to implement.