Google Plus authentication on Android without permissions

You were probably using G+ to sign in into your app.

Maybe you were also requesting a code to authenticate your backend server too.

And for this you were requesting these two permissions:

<!-- Google Plus login -->
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />

If that's your case I have good news: you don't need them anymore.

With Google Play Services 8.3 you can use Auth.GoogleSignInApi to authenticate your user (and your backend) without requesting access to her accounts or contacts.

Here's how to migrate from old system to the new one:

(If you are starting over for the first time you may want to look here for the whole process: Start Integrating Google Sign-In into Your Android App)

1. Add dependency

dependencies {
        compile 'com.google.android.gms:play-services-auth:8.3.0'
    }

2. Configure Google Sign-In and GoogleApiClient

GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
        .requestEmail()
        .build();      

If you also need to authenticate your server:

GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestServerAuthCode(YOUR_SERVER_CLIENT_ID))
                .build();

3. Create GoogleApiClient

 mGoogleApiClient = new GoogleApiClient.Builder(getActivity())
                .enableAutoManage(getActivity(), this)
                .addApi(Auth.GOOGLE_SIGN_IN_API, gso)
                .build();

4. Start Sign-In

private void signIn() {
    Intent signInIntent = Auth.GoogleSignInApi.getSignInIntent(mGoogleApiClient);
    startActivityForResult(signInIntent, RC_SIGN_IN);
}

At this point a native dialog will prompt to ask the user to select an account.

5. Capture Activity Result

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == RC_SIGN_IN && resultCode == Activity.RESULT_OK) {
        GoogleSignInResult result = Auth.GoogleSignInApi.getSignInResultFromIntent(data);
        handleSignInResult(result);
    }
}

private void handleSignInResult(GoogleSignInResult result) {
    if (result.isSuccess()) {
        // Signed in successfully
        GoogleSignInAccount acct = result.getSignInAccount();
        updateUI(acct.getDisplayName())

        // If you are also authenticating your server:
        String code = acct.getServerAuthCode();
        api.loginWithGoogle(code);
    }
}

All your previous G+ auth flow code can be safely thrown to the bin.

Flux Architecture on Android

Finding a good architecture for Android applications is not easy. Google seems to not care much about it, so there is no official recommendation on patterns beyond Activities lifecycle management.

But defining an architecture for your application is important. Like it or not, every application is going to have an architecture. So you'd better be the one defining it than let it just emerge.

Today: Clean Architecture

Current trend is to adapt Clean Architecture, a 2012 Uncle Bob proposal for web applications.

I find Clean Architecture a little bit over-engineered for most of the Android apps out there.

Typically mobile apps live shorter than web apps. Mobile technology is evolving so fast that any app released today is going to be completely deprecated in twelve months.

Mobile apps usually do very little. A very high percent of use cases are just for data consuming. Get data from API, show data to user. Lot of reads, very little writes.

As a result its business logic is not complex. At least not as complex as backend apps. Well you have to deal with platform issues: memory, storage, pause, resume, network, location, etc. But that is not your app business logic. You have all of that in every app.

So it seems that most of the apps out there will not benefit from things like complex layer divisions or job priority execution queues.

They may just need a simple way to organise code, work together efficiently and find bugs easily.

Introducing Flux Architecture

Flux Architecture is used by Facebook to build their client- side web applications. Like Clean Architecture it is not intended for mobile apps, but its features and simplicity will allow us to adapt it very well to Android projects.

flux-graph-simple

There are two key features to understand Flux:

  • The data flow is always unidirectional.

    An unidirectional data flow is the core of the Flux architecture and is what makes it so easy to learn. It also provides great advantages when testing the application as discussed below.

  • The application is divided into three main parts:

    • View: Application interface. It create actions in response to user interactions.
    • Dispatcher: Central hub through which pass all actions and whose responsibility is to make them arrive to every Store.
    • Store: Maintain the state for a particular application domain. They respond to actions according to current state, execute business logic and emit a change event when they are done. This event is used by the view to update its interface.

This three parts communicate through Actions: Simple plain objects, identified by a type, containing the data related to that action.

Flux Android Architecture

The main target of using Flux principles on Android development is to build an architecture with a good balance between simplicity and ease of scale and test.

First step is to map Flux elements with Android app components.

Two of this elements are very easy to figure out and implement.

  • View: Activity or Fragment
  • Dispatcher: An event bus. I will use Otto in my examples but any other implementation should be fine.

Actions

Actions are not complex either. They will be implemented as simple POJOs with two main attributes:

  • Type: a String identifying the type of event.
  • Data: a Map with the payload for this action.

For example, a typical action to show some User details will look like this:

Bundle data = new Bundle();
data.put("USER_ID", id);
Action action = new ViewAction("SHOW_USER", data);

Stores

This is perhaps the most difficult to get Flux concept.

Also if you have worked with Clean Architecture before it also will be uncomfortable to accept, because Stores will assume responsibilities that were previously separated into different layers.

Stores contain the status of the application and its business logic. They are similar to rich data models but they can manage the status of various objects, not just one.

Stores react to Actions emitted by the Dispatcher, execute business logic and emit a change event as result.

Stores only output is this single event: change. Any other component interested in a Store internal status must listen to this event and use it to get the data it needs.

No other component of the system should need to know anything about the status of the application.

Finally, stores must expose an interface to obtain application Status. This way, view elements can query the Stores and update application UI in response.

flux-graph-store

For example, in a Pub Discovery App a SearchStore will be used to keep track of searched item, search results and the history of past searches. In the same application a ReviewedStore will contain a list of reviewed pubs and the necessary logic to, for example, sort by review.

However there is one important concept to keep in mind: Stores are not Repositories. Their responsibility is not to get data from an external source (API or DB) but only keep track of data provided by actions.

So how Flux application obtain data?

Network requests and asynchronous calls

In the initial Flux graph I intentionally skipped one part: network calls. Next graph completes first one adding more details:

flux-graph-complete

Asynchronous network calls are triggered from an Actions Creator. A Network Adapter makes the asynchronous call to the corresponding API and returns the result to the Actions Creator.

Finally the Actions Creator dispatch the corresponding typed Action with returned data.

Having all the network and asynchronous work out of the Stores has has two main advantages:

  • Your Stores are completely synchronous: This makes the logic inside a Store very easy to follow. Bugs will be much easier to trace. And since all state changes will be synchronous testing a Store becomes an easy job: launch actions and assert expected final state.

  • All actions are triggered from an Action Creator: Having a single point at which you create and launch all user actions greatly simplifies finding errors. Forget about digging into classes to find out where an action is originated. Everything starts here. And because asynchronous calls occur before, everything that comes out of ActionCreator is synchronous. This is a huge win that significantly improves traceability and testability of the code.

Show me the code: To-Do App

In this example you will find a classical To-Do App implemented on Android using a Flux Architecture.

I tried to keep the project as simple as possible just to show how this architecture can produce very well organised apps.

Some comments about implementation:

  • The Dispatcher is implemented using Otto Bus. Any bus implementation will mostly work. There is a Flux restriction on events I’m not applying here. On original Flux definition dispatching an event before previous one has finish is forbidden and will throw an exception. To keep the project simple I’m not implementing that here.

  • There is an ActionsCreator class to help creating Actions and posting them into the Dispatcher. It is a pretty common pattern in Flux which keeps things organised.

  • Actions types are just String constants. It is probably not the best implementation but is quick and helps keeping things simple.

Same thing with Actions data: they are just a HashMap with a String key and Object as a value. This forces ugly castings on Stores to extract actual data. Of course, this is not type safe but again, keeps the example easy to understand.

Conclusion

There is no such thing as the Best Architecture for an Android app. There is the Best Architecture for your current app. And it is the one that let you collaborate with your teammates easily, finish the project on time, with quality and as less bugs as possible.

I believe Flux is very good for all of that.

Sample source code

https://github.com/lgvalle/android-flux-todo-app

Further Reading:

Thanks

Special thanks to my colleague Michele Bertoli for taking the time to introduce me to Flux and for reviewing this post.


Token based authentication using Retrofit 1.9 + OkHttp 2.4

Situation is like this:

  1. You got an AccessToken and RefreshToken (AT and RT for now on)
  2. Every API call needs to contain the AT
  3. When the AT expires you need to refresh it using your RT

Interceptor: Attaching Access Token to every network request

What you need is a Network Interceptor. Interceptors are a very powerful mechanism to rewrite calls. After hooking the interceptor to the OkClient every single call will pass through it. That is the perfect moment to attach your AT:

@Override
public Response intercept(Chain chain) throws IOException {
   Request originalRequest = chain.request();

   // Add authorization header with updated authorization value to intercepted request
   Request authorisedRequest = originalRequest.newBuilder()
           .header(AUTHORIZATION, accessToken)
           .build();
   return chain.proceed(authorisedRequest);
}

You can tweak this interceptor to handle edge cases. For example, when your accessToken is empty or when the request already contains an authorization header you may want to skip the rebuild. This can easily be done:

@Override
public Response intercept(Chain chain) throws IOException {
   Request originalRequest = chain.request();

   // Nothing to add to intercepted request if:
   // a) Authorization value is empty because user is not logged in yet
   // b) There is already a header with updated Authorization value
   if (authorizationTokenIsEmpty() || alreadyHasAuthorizationHeader(originalRequest)) {
       return chain.proceed(originalRequest);
   }

   // Add authorization header with updated authorization value to intercepted request
   Request authorisedRequest = originalRequest.newBuilder()
           .header(AUTHORIZATION, authorizationValue)
           .build();
   return chain.proceed(authorisedRequest);
}

Authenticator: Refreshing Access Token

How do you know it is time to refresh your AT? This is the approach:

  • Monitor to network calls looking for errors.
  • Whenever you get a 401 Not Authorised it means your AT is no longer valid.
  • Refresh AT using RT.
  • Repeat failed call.

This can be accomplish with Interceptors as well, but there is a much better way: Authenticators an interface designed specifically for this purpose.

When you set up an Authenticator OkHttp client will automatically ask the Authenticator for credentials when a response is 401 Not Authorised retrying last failed request with provied new credentials.

@Override
public Request authenticate(Proxy proxy, Response response) throws IOException {
   // Refresh your access_token using a synchronous api request
   newAccessToken = service.refreshToken();

   // Add new header to rejected request and retry it
   return response.request().newBuilder()
           .addHeader(AUTHORIZATION, newAccessToken)
           .build();
}

Setting everything up

Create an OkHttpClient and attach your NetworkInterceptor and your Authenticator to it

OkHttpClient okHttpClient = new OkHttpClient();
okHttpClient.networkInterceptors().add(authInterceptor);
okHttpClient.setAuthenticator(authAuthenticator);

Then use this client when creating your Retrofit RestAdapter

RestAdapter restAdapter = new RestAdapter.Builder()
                .setEndpoint(ENDPOINT)
                .setClient(new OkClient(okHttpClient))
                .build();
return restAdapter.create(API.class);


Material Design Animations & Transitions

Android Transition Framework can be used for three main things:

  1. Animate activity layout content when transitioning from one activity to another.
  2. Animate shared elements (Hero views) in transitions between activities.
  3. Animate view changes within same activity.

1. Transitions between Activities

Animate existing activity layout content

A Start B

When transitioning from Activity A to Activity B content layout is animated according to defined transition. There are three predefined transitions available on android.transition.Transition you can use: Explode, Slide and Fade. All these transitions track changes to the visibility of target views in activity layout and animate those views to follow transition rules.

Explode | Slide | Fade --- | --- | --- transition_explode | transition_slide | transition_fade

You can define these transitions declarative using XML or programatically.

FADE SAMPLE

Declarative XML

Transitions are defined on XML files in res/transition

res/transition/activity_fade.xml

<?xml version="1.0" encoding="utf-8"?>
<fade xmlns:android="http://schemas.android.com/apk/res/
    android:duration="1000"/>

res/transition/activity_slide.xml

<?xml version="1.0" encoding="utf-8"?>
<slide xmlns:android="http://schemas.android.com/apk/res/
    android:duration="1000"/>

To use these transitions you need to inflate them using TransitionInflater

MainActivity.java

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

    private void setupWindowAnimations() {
        Slide slide = TransitionInflater.from(this).inflateTransition(R.transition.activity_slide);
        getWindow().setExitTransition(slide);
    }

TransitionActivity.java

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

    private void setupWindowAnimations() {
        Fade fade = TransitionInflater.from(this).inflateTransition(R.transition.activity_fade);
        getWindow().setEnterTransition(fade);
    }

Programatically

MainActivity.java

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

    private void setupWindowAnimations() {
        Slide slide = new Slide();
        slide.setDuration(1000);
        getWindow().setExitTransition(slide);
    }

TransitionActivity.java

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

    private void setupWindowAnimations() {
        Fade fade = new Fade();
        fade.setDuration(1000);
        getWindow().setEnterTransition(fade);
    }

Any of those produce this result:

transition_fade

What is happening step by step:

  1. Activity A starts Activity B

  2. Transition Framework finds A Exit Transition (slide) and apply it to all visible views.

  3. Transition Framework finds B Enter Transition (fade) and apply it to all visible views.

  4. On Back Pressed Transition Framework executes Enter and Exit reverse animations respectively (If we had defined output returnTransition and reenterTransition, these have been executed instead)

ReturnTransition & ReenterTransition

Return and Reenter Transitions are the reverse animations for Enter and Exit respectively.

  • EnterTransition <--> ReturnTransition
  • ExitTransition <--> ReenterTransition

If Return or Reenter are not defined, Android will execute a reversed version of Enter and Exit Transitions. But if you do define them, you can have different transitions for entering and exiting an activity.

b back a

We can modify previous Fade sample and define a ReturnTransition for TransitionActivity, in this case, a Slide transition. This way, when returning from B to A, instead of seeing a Fade out (reversed Enter Transition) we will see a Slide out transition

TransitionActivity.java

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

    private void setupWindowAnimations() {
        Fade fade = new Fade();
        fade.setDuration(1000);
        getWindow().setEnterTransition(fade);

        Slide slide = new Slide();
        fade.setDuration(1000);
        getWindow().setReturnTransition(slide);        
    }

Compare both cases

Without Return Transition | With Return Transition --- | --- transition_fade | transition_fade2

2. Shared elements between Activities

The idea behind this is having two different views in two different layouts and link them somehow with an animation.

Transition framework will then do whatever animations it consider necessary to show the user a transition from one view to another.

Keep this always in mind: the view is not really moving from one layout to another. They are two independent views.

A Start B with shared

a) Enable Window Content Transition

This is something you need to setup once on your app styles.xml.

values/styles.xml

<style name="MaterialAnimations" parent="@style/Theme.AppCompat.Light.NoActionBar">
    ...
    <item name="android:windowContentTransitions">true</item
    ...
</style>

Here you can also specity default enter, exit and shared element transitions for the whole app if you want

<style name="MaterialAnimations" parent="@style/Theme.AppCompat.Light.NoActionBar">
    ...
    <!-- specify enter and exit transitions -->
    <item name="android:windowEnterTransition">@transition/explode</item>
    <item name="android:windowExitTransition">@transition/explode</item>

    <!-- specify shared element transitions -->
    <item name="android:windowSharedElementEnterTransition">@transition/changebounds</item>
    <item name="android:windowSharedElementExitTransition">@transition/changebounds</item>
    ...
</style>

b) Define a common transition name

To make the trick you need to give both, origin and target views, the same android:transitionName. They may have different ids or properties, but transitionName must be the same.

layout/activity_a.xml

<ImageView
        android:id="@+id/small_blue_icon"
        style="@style/MaterialAnimations.Icon.Small"
        android:src="@drawable/circle"
        android:transitionName="@string/blue_name" />

layout/activity_b.xml

<ImageView
        android:id="@+id/big_blue_icon"
        style="@style/MaterialAnimations.Icon.Big"
        android:src="@drawable/circle"
        android:transitionName="@string/blue_name" />

c) Start an activity with a shared element

Use the ActivityOptions.makeSceneTransitionAnimation() method to define shared element origin view and transition name.

MainActivity.java

squareBlue.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent i = new Intent(MainActivity.this, SharedElementActivity.class);

        View sharedView = blueIconImageView;
        String transitionName = getString(R.string.blue_name);

        ActivityOptions transitionActivityOptions = ActivityOptions.makeSceneTransitionAnimation(MainActivity.this, sharedView, transitionName);
        startActivity(i, transitionActivityOptions.toBundle());
    }
});

Just that code will produce this beautiful transition animation:

a to b with shared element

As you can see, Transition framework is creating and executing an animation to create the illusion that the view is moving and changing shape.

To proof the blue square view is not really moving we can do this quick exercise: change transitioName in DetailsActivity from Big Blue Square to the Title Text above it.

<TextView
        android:layout_width="wrap_content"
        android:text="Activity Detail 2"
        style="@style/Base.TextAppearance.AppCompat.Large"
        android:layout_centerHorizontal="true"
        android:transitionName="@string/square_blue_name"
        android:layout_above="@+id/big_square_blue"
        android:layout_height="wrap_content" />

If we now execute the app we have the same behaviour but targeting a different view:

a to b with shared element - 2

3. Animate view layout elements

Transition framework can also be used to animate element changes within current activity layout.

Transitions happen between scenes. An scene defines a static state of our UI. You can do complex things regarding scenes but I want to keep this example as simple as possible.

If you want to know more about scenes I recomend you check this video by Chet Hasse

In this example I'm going to use the easier way to animate layout changes inside an Activity layout:

TransitionManager.beginDelayedTransition(sceneRoot);

With just this line of code we are telling the framework we are going to perform some UI changes that it will need to animate.

After that we made the changes on our UI elements:

setViewWidth(squareRed, 500);
setViewWidth(squareBlue, 500);
setViewWidth(squareGreen, 500);
setViewWidth(squareYellow, 500);

This will change those views width attribute to make it larger. That will trigger a layoutMeasure. At that point the Transition framework will record start and ending values and will create an animation to transition from one to another.

 squareGreen.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                TransitionManager.beginDelayedTransition(sceneRoot);
                setViewWidth(squareRed, 500);
                setViewWidth(squareBlue, 500);
                setViewWidth(squareGreen, 500);
                setViewWidth(squareYellow, 500);
            }
        });
    }

    private void setViewWidth(View view, int x) {
        ViewGroup.LayoutParams params = view.getLayoutParams();
        params.width = x;
        view.setLayoutParams(params);
    }

a to b with shared element - 2

4. (Bonus) Shared elements + Circular Reveal

Circular Reveal is just an animation to show or hide a group of UI elements. It is available since API 21 in ViewAnimationUtils class.

In this example I'm going to demostrate how can you make use of Shared Element Transition and Circular Reveal Animation to smoothly switch UI context.

shared+circularreveal

Enter Animation

What is happening step by step is:

  • Shared orange box is transitioning from MainActivity to DetailsActivity.
  • DetailsActivity background viewgroup visibility starts as INVISIBLE.
 <RelativeLayout
        android:layout_width="match_parent"
        android:id="@+id/backgroundViewGroup"
        android:visibility="invisible"
        ...
  • After SharedElementEnterTransition ends a CircularReveal animation takes place making the background viewgroup visible.
        Transition enterTransition = getWindow().getSharedElementEnterTransition();
        enterTransition.addListener(new Transition.TransitionListener() {
            @Override
            public void onTransitionStart(Transition transition) {}

            @Override
            public void onTransitionEnd(Transition transition) {
                animateRevealShow(bgViewGroup);
            }

            @Override
            public void onTransitionCancel(Transition transition) {}

            @Override
            public void onTransitionPause(Transition transition) {}

            @Override
            public void onTransitionResume(Transition transition) {}
        });

Exit Animation

On exit transition steps are:

  • SharedElementReturnTransition is delayed 1 second.
        Transition sharedElementReturnTransition = getWindow().getSharedElementReturnTransition();
        sharedElementReturnTransition.setStartDelay(ANIM_DURATION);
  • ReturnTransition duration is setted to 1 second. Have in mind this are two different transitions.
        Transition returnTransition = getWindow().getReturnTransition();
        returnTransition.setDuration(ANIM_DURATION);
  • On ReturnTransition start a CircularReveal animation takes place hiding the background viewgroup.
        returnTransition.addListener(new Transition.TransitionListener() {
            @Override
            public void onTransitionStart(Transition transition) {
                animateRevealHide(bgViewGroup);
            }

            @Override
            public void onTransitionEnd(Transition transition) {}

            @Override
            public void onTransitionCancel(Transition transition) {}

            @Override
            public void onTransitionPause(Transition transition) {}

            @Override
            public void onTransitionResume(Transition transition) {}
        });
  • After 1 second, CircularReveal has finished and SharedElementReturnTransition gets executed producing orange box animation.

Sample source code

https://github.com/lgvalle/Material-Animations

More information

How ButterKnife actually works?

You all know ButterKnife: the brilliant annotation processing library to bind views and methods for Android by @JakeWharton

But, how does it work? Most people think by just adding new methods to our classes, saving us a quite a lot of boiler plate code.

Well, it’s definitely saving us some code, but you may be surprised to discover that is not changing our classes at all.

Want to find out? First, you need a quick overview of how annotation processing works in java.

Java Annotation Processing

Annotation processing is a tool build in javac for scanning and processing annotations at compile time.

You can define your own annotations and a custom processor to handle them.

  • Annotations are scanned and processed at compile time.

  • An Annotation Processor reads java code, process its annotations and generate java code in response.

  • Generated java code is then compiled again as a regular java class

  • An Annotation Processor can not change an existing input java class. Neither adding or modifiying methods.

Java Compiler

At OpenJDK you can read an excellent overview of how Java compiler works.

This chart summarizes very well the part that interests us:

java compiler Java compiling process overview

ButterKnife workflow

When you compile your Android project ButterKnife Annotations Processor process method is executed, doing the following:

  • First, it scans all java classes looking for ButterKnife annotations: @InjectView, @OnClick, etc.

  • When it find a class with any of these annotations it creates a file called: <className>$$ViewInjector.java

  • This new ViewInjector class contains all the neccessary methods to handle annotation logic: findViewById, view.setOnClickListener, etc.

  • Finally, during execution, when we call ButterKnife.inject(this) each ViewInjector inject method is called.

Sample

For the sample code you can find at https://github.com/JakeWharton/butterknife this is what happens underneath:

butterknife sample

ExampleActivity.java

class ExampleActivity extends Activity {
  @FindView(R.id.user) EditText username;
  @FindView(R.id.pass) EditText password;

  @OnClick(R.id.submit) void submit() {
    // TODO call server...
  }

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    ButterKnife.bind(this);
    // TODO Use fields...
  }
}

During compile time this java class will be generated:

ExampleActivity$$ViewBinder.java

public class ExampleActivity$$ViewBinder<T extends com.lgvalle.samples.ui.ExampleActivity> implements ViewBinder<T> {
  @Override public void bind(final Finder finder, final T target, Object source) {
    View view;
    view = finder.findRequiredView(source, 2131361865, "field 'user'");
    target.username = finder.castView(view, 2131361865, "field 'user'");
    view = finder.findRequiredView(source, 2131361868, "field 'pass'");
    target.password = finder.castView(view, 2131361868, "field 'pass'");

    view = finder.findRequiredView(source, 2131361874, "field 'submit' and method 'submit'");
    view.setOnClickListener(
      new butterknife.internal.DebouncingOnClickListener() {
        @Override public void doClick(android.view.View p0) {
          target.submit();
        }
      });
  }

  @Override public void reset(T target) {
    target.username = null;
    target.password = null;
  }
}

Then, during execution time, when we call ButterKnife.bind(this); what happens is:

  • ButterKnife calls findViewBinderForClass(ExampleActivity.class) finding ExampleActivity$$ViewBinder.java
  • ExampleActivity$$ViewBinder.bind() is executed, finding and casting views and setting them into ExampleActivity.class attributes, which are public
  • onClickListeners for views are setted up as a wrapper to execute target defined method to handle clicks (annotated with @OnClick)

This is why annotated attributes and methods must be public: ButterKnife needs to be able to access them from a separate class.

More info

If you want to know more about Java Annotation Processing, this three post help me out a lot when writing this post: