Introducing Hijckr: Android XML Tags router

“Hijckr: Misappropriating your XML Tags since 2018”

Hijckr is a new Android library that allows developers to intercept XML layout file inflation and reroute XML elements to whatever widgets they’d like. That allows developers to use their existing layout files while modifying widget behavior and display. Hijckr supports rerouting preferences an app-wide basis or per-activity.

Getting Started: gradle

To get started create a new Android app and add a dependency on the Hijckr library in the build.gradle file:

dependencies {
// ...
implementation 'com.justinangel:hijckr:1+'
}

Hijckr Example: App-wide Tag Rerouting

In the simplest case, we’d like to execute additional code in the lifecycle of existing widgets. We’ll do that by subclassing that widget and rerouting away from the default widget to our inheriting widget. All wehave to do for that is setup class routing by invoking HijckrClassLoader.addGlobalClassRouting and have my activity inherit from HijckrActivity.

As a trivial example, let’s say all our <TextView /> xml tags should instantiate com.my.AppTextView that changes text color to red and font size to 18.

We start off with an XML layout file using <TextView />.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!" />
</LinearLayout>

Next we’ll create the AppTextView class.

public class AppTextView extends TextView {
// .. c'tors

@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
this.setTextSize(28);
this.setTextColor(Color.RED);
}
}

Now for sprinkling Hijckr magic. First, we’ll tell Hijckr to route all XML tags belonging to TextView to AppTextView. We’ll setup those rerouting in a custom application onCreate().

public class App extends Application {
@Override
public void onCreate() {
super.onCreate();

HijckrClassLoader.addGlobalClassRouting(TextView.class, AppTextView.class);
}
}

For the other bit of Hijckr fairy dust we’ll change our activity to inherit from HijckrActivity as a base class instead of Activity.

import com.justinangel.hijckr.HijckrActivity;
public class MainActivity extends HijckrActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}

And when we run our app we can see our <TextView />uses the new AppTextView because the text is red and the font size is 18.

Hijckr: Activity-Specific Tag Rerouting

There are usecases where we wouldn’t want to setup app-wide rerouting or we can’t just change our activity base class. For those cases, we can setup activity-specific tag rerouting.

As a trivial exmaple, let’s say that we want all <EditText /> within a specific activity to find any “:)” strings and replace it with a 😀 emoji. We start off with a layout file that has an <EditText /> tag.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">

<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Type a smiley face"/>
</LinearLayout>

We then write a custom widget that inherits from EditText and adds our desired extra behavior.

public class SmileyEditText extends EditText {
// .. c'tors
    @Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
super.onTextChanged(text, start, lengthBefore, lengthAfter);

if (text.toString().contains(":)")) {
setText(text.toString().replace(":)", "\uD83D\uDE0A"));
}
}
}

Within our activity we’ll override getClassLoader to return a new HijckrClassLoader with our desired rerouting.

import com.justinangel.hijckr.HijckrClassLoader;

public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}

@Override
public ClassLoader getClassLoader() {
return new HijckrClassLoader(super.getClassLoader())
.withClassRouting(EditText.class, S.class);
}
}

When we run this sample and type in “:)” we can see the text has been replaced.

When should I use Hijckr? What are the best practices?

I’ve been using Hijckr-like tag rerouting for a few years now and we’ve come up with a few best practices in our apps.

  • Do add behavior to existing widgets using Hijckr by adding code to c’tors or Android lifecycle overrides.
  • Do use the default Android Studio designer and widgets. Hijckr let’s you keep using the default widgets in the default designer.
  • Don’t use Hijckr exclusively for styling. Android has great resource management system that supports styling and theming. You don’t want to give that up.
  • Do use Hijckr to change style & behavior for widgets conditionally. For example app running on Android Wear with layouts that contain <RecyclerView /> tags shuld be rerouted to WearableRecyclerView. That’s the usecase that first brought me to Hijckr.

Known Limitations

  • Instantiated elements: changing global routing only applies before the layout files have been inflated. It won’t be applied retroactively to instantiated elements after layout inflation.
  • Named elements: For XML elements with x:Name rerouting must be towards classes inheriting from that original routing. e.g. <Button x:Name=”foo” /> must be routed to a class that has Button as a base class. For any XML elements that don’t have x:Name rerouting don’t have this constraints. For e.g. <Button /> can be routed to anything (e.g. TextView).

Questions?

Ask away.

Check out the source on GitHub.