Thursday, March 14, 2019

Auto views references release for Fragments

Take a look at this code:

class MyFragment : Fragment() {

    private var someView: View? = null

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.f_my_fragment, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        someView = view.findViewById(R.id.view)
    }

}

What do you think? Is everything fine here? I'll give you a minute :)
Nope, there is a memory leak. Our someView variable will become a memory leak when fragment goes to back stack. Ideally we must remove all references to views in onDestroyView() method:

class MyFragment : Fragment() {

    private var someView: View? = null

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.f_my_fragment, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        someView = view.findViewById(R.id.view)
    }

    override fun onDestroyView() {
        super.onDestroyView()
        
        someView = null
    }

}

Now we have fixed this memory leak. But this process can become cumbersome when we deal with multiple views. Also it is pretty easy to forget to release all references.

But we can do better!

So I wrote this property delegate which automatically releases all references to views:

private class FragmentViewProperty<T : View> : ReadWriteProperty<Fragment, T>, LifecycleObserver {

    private var view: T? = null
    private var lifecycle: Lifecycle? = null

    override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
        return view ?: throw NullPointerException()
    }

    override fun setValue(thisRef: Fragment, property: KProperty<*>, value: T) {
        view = value

        lifecycle?.removeObserver(this)
        lifecycle = thisRef.viewLifecycleOwner.lifecycle.also {
            it.addObserver(this)
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    private fun onViewDestroyed() {
        view = null
        lifecycle?.removeObserver(this)
        lifecycle = null
    }
}

fun <T: View> fragmentView(): ReadWriteProperty<Fragment, T> = FragmentViewProperty()

And now we can rewrite our code:

class MyFragment : Fragment() {

    private var someView by fragmentView<View>()

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.f_my_fragment, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        someView = view.findViewById(R.id.view)
        someView.requestFocus() // as an added benefit we don't need to do null-checks now
    }
}

Hope this was helpful. Cheers.

Wednesday, September 13, 2017

Fragments everywhere !!!

Each developer sometimes wants to create something "innovative" and stupid just for the sake of it. And it came to me - why not to put fragments directly into views (and in result in popup windows and dialogs) avoiding activity.

Actually I had requirement on my project that was impossible to finish without this "genius" idea because of some ugly legacy code written by other developer (sure it was not me, my code is perfect :-D).

After digging support library for some time and implementing my own FragmentManager and FragmentTransaction I noticed one important detail - there is no need to reinvent a weal, it was already invented by Android's team. All I had to do is to look at FragmentActivity and find the FragmentController (silly me, I had to look there from the beginning). One interesting thing is that FragmentController is a public class which [in theory] means that we can safely use it.

Ok, so no more talk. Here is my implementation of FragmentManagerView:

    public class FragmentManagerView extends FrameLayout {

        private Activity mActivity;
        private FragmentController mFragments;
        private Handler mHandler = new Handler(Looper.getMainLooper());

        public FragmentManagerView(Context context) {
            this(context, null);
        }

        public FragmentManagerView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);

            while (context != null) {
                if (context instanceof Activity) {
                    mActivity = (Activity) context;
                    break;
                }
                if (context instanceof ContextWrapper) {
                    context = ((ContextWrapper) context).getBaseContext();
                } else {
                    break;
                }
            }

            setId(getFragmentRootId());

            mFragments = FragmentController.createController(new HostCallbacks());
            mFragments.attachHost(null);
        }

        @Override
        protected void onAttachedToWindow() {
            super.onAttachedToWindow();

            mFragments.dispatchCreate();
            mFragments.dispatchActivityCreated();
            mFragments.onCreateView(this, "", getContext(), null);
            mFragments.dispatchStart();
            mFragments.dispatchResume();
            mFragments.execPendingActions();
        }

        @Override
        protected void onDetachedFromWindow() {
            mFragments.execPendingActions();
            mFragments.dispatchPause();
            mFragments.dispatchStop();
            mFragments.dispatchReallyStop();
            mFragments.dispatchDestroyView();
            mFragments.dispatchDestroy();

            super.onDetachedFromWindow();
        }

        public int getFragmentRootId() {
            return android.R.id.content;
        }

        public FragmentManager getFragmentManager() {
            return mFragments.getSupportFragmentManager();
        }

        private class HostCallbacks extends FragmentHostCallback<FragmentManagerView> {
            HostCallbacks() {
                super(mActivity, mActivity, mHandler, 0);
            }

            @Override
            public boolean onHasView() {
                return true;
            }

            @Override
            public View onFindViewById(int id) {
                return findViewById(id);
            }

            @Override
            public FragmentManagerView onGetHost() {
                return FragmentManagerView.this;
            }

            @Override
            public LayoutInflater onGetLayoutInflater() {
                return mActivity.getLayoutInflater().cloneInContext(mActivity);
            }
        }
    }

And this is how you can use it:

    public class MyInnovativeView extends FragmentManagerView {
        @Override
        protected void onAttachedToWindow() {
            super.onAttachedToWindow();

            getFragmentManager().beginTransaction()
                    .replace(getFragmentRootId(), MyFragment.newInstance())
                    .commit();
        }
    }

    public class MyFragment extends Fragment {
        // ...
    }

It works almost flawlessly. This solution even works for popup windows and dialogs. But there are still few drawbacks (actually those are all downsides that I found so far):

  • getActivity() returns null in all fragments

    This one is what I actually don't understand. FragmentHostCallback has constructor to provide activity (which then will be returned via Fragment.getActivity()) but it has package access. Same for FragmentHostCallback.getActivty() method. Because of this you can't legitimately use it.

    But where is a will, there is a way!
    We only need to place our FragmentManagerView in android.support.v4.app and by magic of great Java we'll get required access.
  • no state saving

    It can be easily implemented using FragmentController.saveAllState() / View.onSaveInstanceState() and View.onRestoreInstanceState() / FragmentController.restoreAllState()


And now few final words - DON'T USE IT

  • Yes, I used it on my project.
  • Yes, all required classed are available and public.
But

  • No, it does mean that you can safely use it and it will work for all cases.
  • No, I don't know if this API was designed for such cases.
  • No, there are no quranties that nothing will be changed in the future.

And most importantly

  • Don't code in ways that force you to use solutions like this.


Have a nive coding ;)

Thursday, September 10, 2015

Fun with Transitions

Do you want to add custom transitions to fragments or any other view hierarchy change?

Me too. I want an easy way to add custom animations to any view hierarchy change. And also I want it to be easy to use. Actually I like everything to be easy to use.

Lets start with regular views. To add fancy custom animation we need to take content's snapshot before hierarchy change and in some cases after. Not all animations require snapshot of final state (for ex. look at RevealTransition.java).

Now about fragments. Essentially most of the time they represent views and it means that they can be animated same way. But there is a catch - fragment manager will change hierarchy only on next Looper pass. It means that we need to:
  • skip first frame drawn
  • or to call FragmentManager.executePendingTransactions();

This is how my implementation looks like:



Now about the code. Most of the work is done inside TransitionView. All you need to do is to pass any Transition object via TransitionView.startTransition(), make desired hierarchy change and it will do the rest.
  mTransitionView.startTransition(new RevealTransition(anchor));

  getSupportFragmentManager().beginTransaction()
      .replace(R.id.content, MainFragment.newInstance())
      .commit();
Transition itself is a very simple class that is responsible for actually drawing each frame. It also has few callbacks:
  • onStarting - called right after TransitionView.startTransition() (initial image can be taken here)
  • onStarted - called when hierarchy was changed (final image can be taken here)
  • onEnding - called before last animation frame
  • onEnded - called after last animation frame was drawn

It also has Transition.draw() method that is responsible for actual animation drawing.

Have fun! :)

https://github.com/MatrixDev/FunWithTransitions

Thursday, April 2, 2015

Fun with Parallax


Do you like fancy parallax effect?
Do you want to add it with ease to any view?

So do I. My first steps were to extend View and draw everything myself. Later I've tried to extend ImageView and play with its Matrix. But I always wanted an easier solution. Without changing each view that supposed to have one. And here what I came up with.



There is only one solution to support any view/hierarchy without changing it - we need custom layout that will add parallax effect to its content. Idea is following:


FrameLayout will emulate more space for its content. So for example if FrameLayout width is 100px it will position its content as if it had 150px. This extra space is required for parallax animation. This is how it can be done:
  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(
        updateMeasureSpec(widthMeasureSpec),
        updateMeasureSpec(heightMeasureSpec)
    );

    int width = (int) (getMeasuredWidth() / (1 + 2 * mStrength));
    int height = (int) (getMeasuredHeight() / (1 + 2 * mStrength));

    setMeasuredDimension(width, height);
  }

  @Override
  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    mOffsetX = (int) ((right - left) * mStrength);
    mOffsetY = (int) ((bottom - top) * mStrength);

    super.onLayout(changed, left - mOffsetX, top - mOffsetY, right + mOffsetX, bottom + mOffsetY);

    for (int index = getChildCount() - 1; index >= 0; --index) {
      View view = getChildAt(index);
      view.offsetLeftAndRight(-mOffsetX);
      view.offsetTopAndBottom(-mOffsetY);
    }
  }

  /*
   * if we have EXACTLY - emulate more space for measuring
   * if we have anything else - measure content and than decrease our size based on it
   */
  private int updateMeasureSpec(int measureSpec) {
    int mode = MeasureSpec.getMode(measureSpec);
    if (mode != MeasureSpec.EXACTLY) {
      return measureSpec;
    }
    int size = MeasureSpec.getSize(measureSpec);
    int offset = (int) (size * mStrength);
    return MeasureSpec.makeMeasureSpec(size + offset * 2, mode);
  }
Now we need to offset content. We can do this in scroll callback from ViewTreeObserver but I prefer to use pre-draw callbacks. This way offsets will be updated even during transition animations.
  @Override
  protected void onAttachedToWindow() {
    super.onAttachedToWindow();

    getViewTreeObserver().addOnPreDrawListener(this);
  }

  @Override
  protected void onDetachedFromWindow() {
    super.onDetachedFromWindow();

    getViewTreeObserver().removeOnPreDrawListener(this);
  }

  @Override
  public boolean onPreDraw() {
    getLocationInWindow(mLocationCache);

    View root = getRootView();
    int width = getWidth();
    int height = getHeight();
    int rootWidth = root.getWidth();
    int rootHeight = root.getHeight();

    // parallax effect [0..1]
    float parallaxX = width < rootWidth ? mLocationCache[0] / (float) (rootWidth - width) : .5f;
    float parallaxY = height < rootHeight ? mLocationCache[1] / (float) (rootHeight - height) : .5f;

    // parallax offset [-value..+value]
    float offsetX = (parallaxX * 2 - 1f) * mOffsetX;
    float offsetY = (parallaxY * 2 - 1f) * mOffsetY;

    for (int index = getChildCount() - 1; index >= 0; --index) {
      View view = getChildAt(index);
      view.setTranslationX(-offsetX);
      view.setTranslationY(-offsetY);
    }

    return true;
  }
Thats it! Now all content inside our layout will have parallax effect.

Sources: https://github.com/MatrixDev/FunWithParallax

Monday, March 30, 2015

Fun with SQLite and ORMLite

Need to store complex objects in SQLite with ease?
Need document-based database like MongoDB?

It's actually very easy to do. This approach is intended to be used in Android but also works well outside. Just to be clear - this article describes how to store complex objects with tons of sub-objects, arrays and other stuff in DB without need of multiple tables and foreign keys. This is ideal approach for beginners and applications with lightly-loaded DBs.

Disclaimer: don't use it if you have simple objects, heavy-loaded DBs or if you need relations between tables. In these cases it will gain nothing but negative impact on performance.

So if you're still here lets take for example following class:
class ComplexData {
  public String id;
  public long timestamp;

  // some complex data here
}
Depending on how complex data is, it might require few tables with foreign keys. But we can go another way. Create a table with id, timestamp and text columns and write serialized object into text column. Id and timestamp columns are taken just as an example in case we need to search objects by both values.

We'll need DAO and DBO in case we're using ORMLite. DBO will look something like this:
@DatabaseTable(tableName = "complex_data")
class ComplexDataDbo {
  private ComplexData mObject;

  @DatabaseField(id = true, useGetSet = true)
  private String id;

  @DatabaseField(useGetSet = true)
  private long timestamp;

  @DatabaseField(useGetSet = true)
  private String json;

  private ComplexDataDbo() {
  }

  public ComplexDataDbo(ComplexData data) {
    mObject = data;
  }

  public ComplexData getComplexData() {
    return mObject;
  }

  public String getId() {
    return mObject != null ? mObject.id : "";
  }

  public void setId(String id) {
  }

  public long getTimestamp() {
    return mObject != null ? mObject.timestamp : 0;
  }

  public void setTimestamp(long timestamp) {
  }

  public String getJson() {
    return PackUtils.pack(mObject); // I'm using Jackson for serialization
  }

  public void setJson(String json) {
    mObject = PackUtils.unpack(ComplexData.class, json);
  }
}
Now we need to create DAO. It is very simple with help of ORMLite:
private Dao<ComplexDataDbo, String> mComplexDataDao;

// ...

ConnectionSource connection = new AndroidConnectionSource(...);

mComplexDataDao = DaoManager.createDao(connection, ComplexDataDbo.class);
TableUtils.createTableIfNotExists(connection, ComplexDataDbo.class);
Now we can add objects to DB like this:
mComplexDataDao.createOrUpdate(new ComplexDataDbo(complexData));
And fetch them:
ComplexData complexData = mComplexDataDao.queryForId(id).getComplexData();
For more advanced search we can use:
List<ComplexDataDbo> dbos = mComplexDataDao.queryBuilder()
    .offset(offset).limit(limit)
    .where().lt("timestamp", timestamp)
    .query();
Thats it! I've already used this approach with success in two project so be sure - it is tested and performs well.

Thursday, March 26, 2015

Fun with Parcelables

Imagine that you have a task to create wizard-like application on Android which consists of three pages and user must fill some information about himself on each page.

Basic implementation will contain Model and three Activities.

Model will look like this:
public class Model implements Serializable { // or Parcelable, it doesn't matter
  // Page #1 info
  public String nameFirst;
  public String nameLast;

  // Page #2 info
  public long birthday;

  // Page #3 info
  public String address;
}

And each activity will contain these lines:
public class Activity{N} extends Activity {
  
  private Model mModel;
  
  @Override
  public void onCreate(Bundle savedInstanceState) {
    mModel = (Model) getIntent().getSerializableExtra("model");
    // ...
  }

  private void goToNextScreen() {
    startActivity(new Intent(this, Activity{N+1}.class).putExtra("model", mModel));
  }

  // ...  
}

So far so good.

So whats wrong here?

Now imagine that user has filled form on screen #3 and went back to change something on screen #1. Everything seems OK until user goes forward again. All entered information on following screens will disappear. This is happening because Model is actually copied between activities and we're not updating previous Models with new information.



It can be solved, for example, by using startActivityForResult and passing newer Model as Activity's result in onBackKeyPressed method. This approche is good if we have simple data and few screens but in big projects it becames a nightmare to manage it.

We need some automated solution that is easy to use.

Lets play with Parcelables

A lot of people out there hate Parcelable. And they are not wrong - you have to write tons of code for each class for serialization and it is very easy, for example, to miss that one variable.

But Parcelable has one property that I always liked - Creator. With its help we can control not only object serialization but allocation as well.

Idea is following - after creating Model, we put it into a weak cache. During deserialization we will just take Model from that cache. If no Model is found there - just proceed with typical deserialiation. This way all Activities will share the same Model instance and if Activity was killed by Android - we just deserialize it.



For this we will need cache with weak references and base Parcelable class. For actual serialization I will use Jackson library but it can be anything (for example Serializable). For me Jackson library is a preferred way just because it is highly configurable.

Here is our base Parcelable class:
public class JsonParcelable implements Parcelable {

  private static final WeakCache<JsonParcelable> sCache = new WeakCache<>();

  @JsonIgnore
  private String mId;

  protected JsonParcelable() {
    mId = UUID.randomUUID().toString();
    sCache.put(getClass().getName() + "%" + mId, this);
  }

  @Override
  public int describeContents() {
    return 0;
  }

  @Override
  public void writeToParcel(Parcel parcel, int flags) {
    parcel.writeString(mId);
    parcel.writeString(getClass().getName());
    parcel.writeString(PackUtils.pack(this)); // serialize to JSON here
  }

  public static final Creator<JsonParcelable> CREATOR = new Creator<JsonParcelable>() {
    @Override
    public JsonParcelable createFromParcel(Parcel parcel) {
      try {
        String id = parcel.readString();
        String className = parcel.readString();
        String json = parcel.readString();

        synchronized (sCache) {
          JsonParcelable object = sCache.get(className + "%" + id);
          if (object != null) {
            return object;
          }
          return (JsonParcelable) PackUtils.unpack(Class.forName(className), json); // deserialize from JSON here

        }
      } catch (Exception ex) {
        throw new RuntimeException(ex);
      }
    }

    @Override
    public JsonParcelable[] newArray(int size) {
      return new JsonParcelable[size];
    }
  };
}
And weak cache class:
public class WeakCache<T> {

  private Map<String, Ref<T>> mObjects = new HashMap<>();
  private ReferenceQueue<T> mQueue = new ReferenceQueue<>();

  public T get(String id) {
    Ref ref;
    while ((ref = (Ref) mQueue.poll()) != null) {
      mObjects.remove(ref.id);
    }

    T object = null;

    Ref<T> refT = mObjects.get(id);
    if (refT == null || (object = refT.get()) == null) {
      mObjects.remove(id);
    }
    return object;
  }

  public T put(String id, T object) {
    mObjects.put(id, new Ref<>(id, object, mQueue));
    return object;
  }

  static class Ref<T> extends WeakReference<T> {
    public final String id;

    Ref(String id, T r, ReferenceQueue<? super T> q) {
      super(r, q);
      this.id = id;
    }
  }
}

How to use it?

It is very easy to use it. All you need to do is just to extend JsonParcelable and pass that object as Parcelable through Intent. Everything else is done automatically.

So our model will look like this:
public class Model extends JsonParcelable {
  // ...
}

To start Activity just use:
startActivity(new Intent(...).putExtra(mModel));

And to get Model inside Activity:
mModel = getIntent().getParcelableExtra("model");

Thats it!!!

Thursday, December 25, 2014

Android's alternative IPC approche

I hate Android's Services

To be more precise - I hate asynchronous nature of bindService method. Sometimes it is much more convenient to have synchronous way to get AIDL object.

Did you know that almost each Android's *Manager has its own *ManagerService object?
For example ActivityManager and ActivityManagerService or WindowManager and WindowManagerService, etc.

I was always curious about them. You can get each Manager through Context.getSystemService method and it works synchronously. It would be a waste of time and memory to bind all of them at application startup so they must be bound during getSystemService call. After some digging I found out that they are and it's done through hidden "global context object" which is Binder.


How is Binder connected to AIDL?

Generated AIDL Stub extends Binder and overrides onTransact method, while AIDL Proxy wraps Binder and calls its transact method. Actually Binder is just a "gateway" that passes bytes in one direction, so all function calls are serialized into a byte array, passed through a Binder and than deserialized.

So the problem is only to pass Binder and Parcel can do it. If Parcel can - Bundle can do it either. And here it struck me - Binder can be passed through Intents and ContentProviders.


Fun with Intents

You can also have some fun with Intents. I don't know why someone would start Activity and pass AIDL through Intent but it is possible to pass AIDL object to IntentService and track that task's progress.

But Intents are of no interest to me now.


Fun with ContentProviders (CP)

For a long time I hated CPs and their DB-related interface until I found interesting function - ContentProvider.exec. It has simple interface and can receive/return Bundles. And with bundles you can pass complex objects, for example AIDL objects. And it turns out that CPs are synchronous.

Eureka!!! Here is a way to synchronously "bind" to another process. You can just create AIDL stub and return it through CP.

For example this is my AIDL interface:
interface ICentral {
    int getRequestsCount();
    void sendRandomRequest(long userTime, String inputArg, ICallback callback);
}

And my ContentProvider:
public class CentralProvider extends ContentProvider {

    // ...

    @Override
    public Bundle call(String method, String arg, Bundle extras) {
        if (extras != null) {
            extras.setClassLoader(ParcelableBinder.class.getClassLoader());
        }
        switch (Central.Method.valueOf(method)) {
            case GetServiceBinder:
                Bundle bundle = new Bundle();
                bundle.putParcelable("binder", new ParcelableBinder((IBinder) CentralBinder.self));
                return bundle;
        }
        return super.call(method, arg, extras);
    }
}

Now to "client" side. To simplify things I need a wrapper:
public class Central {

    public static final Uri AUTHORITY = Uri.parse("content://dev.matrix.central/");

    private static ICentral sCentral;

    public static ICentral getProxy() {
        if (sCentral == null || !sCentral.asBinder().pingBinder()) {
            Bundle bundle = BaseApp.self().getContentResolver().call(AUTHORITY, Method.GetServiceBinder.name(), null, null);
            bundle.setClassLoader(ParcelableBinder.class.getClassLoader());
            sCentral = ICentral.Stub.asInterface(ParcelableBinder.getBinder(bundle, "binder"));
        }
        return sCentral;
    }
}

And here is how to use it:
int count = 0;
try {
    count = Central.getProxy().getRequestsCount();
} catch (Exception ex) {
    // ...
}

Good:
  • full AIDL support
  • fully synchronous "binding" to remote process
  • seamless handing and reconnected of dead Binders

Bad:
  • you don't have service "connection" so remote process can be killed at any time unless at least one service is running

Performance (tested on Nexus 5 with Android 5.0):
  • AIDL method invokes - 0.2-0.5 ms
  • Binder pinging to check if it is alive - 0.1-0.3 ms
  • hot "bind" (if remote app is running) - 1-2 ms
  • cold "bind" (if remote app is down) - 50-100 ms