Cheeseknife! It's like a weird Butter Knife!
Inspired by the Java Butter Knife
library for Android, Cheeseknife is a view injection library for Xamarin.Android to ease the pain of manually resolving each and every one of your Android views and events in your view lifecycles. Injection occurs at runtime rather than compile time and uses C# attributes to mark Android view member fields for injection.
Key coolness features of this library include:
- No longer need to use
FindViewById
repeatedly in your init lifecycles - simply annotate Android view fields with[InjectView(Resource.Id.some_resource)] TextView textView
. - Easily inject commonly used view events such as
Click
. For example:[InjectOnClick(Resource.Id.some_resource)] void IWasClicked(object sender, EventArgs e) { ... }
. See the Supported Cheeseknife Events section below for the list of available Cheese Knife events. Use of event injection is completely optional - you can continue to use your own lambdas/event handlers etc if you want. - Helps to keep the boilerplate clutter out of your view initialisation/cleanup code.
If you like this library or find it useful, feel free to send good thoughts in my general direction!
Cheeseknife is not compatible with code that is inside an Android Library project. This is due to how Android Library resource identifiers are not final (whereas resource identifiers in non-library projects are final), which is a requirement of using Cheeseknife annotations.
The same problem happens in Java Android dev with Android Library projects as well, for example Butterknife (which Cheeseknife is inspired from) cannot auto inject UI fields in a library project either. Visit this link for more information about non-final resource identifiers in Android Library projects:
http://tools.android.com/tips/non-constant-fields
Butterknife should work just fine for any regular Android projects though.
There are a few ways to include Cheeseknife in your own Xamarin.Android project:
- Option 1: Copy the
Cheeseknife
library project from the demo solution and add it as a reference to your Android project. - Option 2: Copy the
Cheeseknife.dll
andCheeseknife.xml
files from theBinaries
folder in the demo solution to your own GAC folder and include the DLL in your assembly references. - Option 3: Copy the
Cheeseknife.cs
source file into your own project and add it into your solution.
public class ExampleActivity : Activity {
[InjectView(Resource.Id.myTextView)]
TextView textView;
[InjectOnClick(Resource.Id.myButton)]
void OnClickMyButton(object sender, EventArgs e) {
// This code will run when the button is clicked ...
}
protected override void OnCreate(Bundle bundle) {
base.OnCreate(bundle);
SetContentView(Resource.Layout.main_activity);
Cheeseknife.Inject(this);
textView.Text = "This text view reference was injected!";
}
}
public class ExampleFragment : Fragment {
[InjectView(Resource.Id.list_view)]
ListView listView;
[InjectOnItemClick(Resource.Id.list_view)]
void OnListViewItemClick(object sender, AdapterView.ItemClickEventArgs e) {
// This code will run when a list item is clicked ...
}
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
var view = inflater.Inflate(Resource.Layout.list_sample_fragment, null);
Cheeseknife.Inject(this, view);
// Do your fragment initialisation here, all views will be available...
return view;
}
public override void OnDestroyView() {
base.OnDestroyView();
Cheeseknife.Reset(this);
}
}
public class ListSampleAdapter : BaseAdapter {
public override View GetView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if(convertView == null) {
convertView = LayoutInflater
.From(MainApplication.Context)
.Inflate(Resource.Layout.list_sample_list_item, parent, false);
viewHolder = new ViewHolder(convertView);
convertView.Tag = viewHolder;
} else {
viewHolder = (ViewHolder)convertView.Tag;
}
viewHolder.TitleView.Text = listData[position];
return convertView;
}
class ViewHolder : Java.Lang.Object {
[InjectView(Resource.Id.title_text_view)]
public TextView TitleView { get; private set; }
public ViewHolder(View view) {
Cheeseknife.Inject(this, view);
}
}
}
Cheeseknife supports some of the most common Android view events:
Click Event - applied to View objects
[InjectOnClick(Resource.Id.some_view)]
void SomeMethodName(object sender, EventArgs e) { ... }
Touch Event - applied to View objects
[InjectOnTouch(Resource.Id.some_view)]
void SomeMethodName(object sender, View.TouchEventArgs e) { ... }
Long Click Event - applied to View objects
[InjectOnLongClick(Resource.Id.some_view)]
void SomeMethodName(object sender, View.LongClickEventArgs e) { ... }
Item Click Event - applied to AdapterView objects
[InjectOnItemClick(Resource.Id.some_list_view)]
void SomeMethodName(object sender, AdapterView.ItemClickEventArgs e) { ... }
Item Long Click Event - applied to AdapterView objects
[InjectOnItemLongClick(Resource.Id.some_list_view)]
void SomeMethodName(object sender, AdapterView.ItemLongClickEventArgs e) { ... }
Focus Change Event - applied to View objects
[InjectOnFocusChange(Resource.Id.some_view)]
void SomeMethodName(object sender, View.FocusChangeEventArgs e) { ... }
Checked Change Event - applied to CompoundButton objects
[InjectOnCheckedChange(Resource.Id.some_compound_button_view)]
void SomeMethodName(object sender, CompoundButton.CheckedChangeEventArgs e) { ... }
Text Changed Event - applied to TextView objects
[InjectOnTextChanged(Resource.Id.some_text_view)]
void SomeMethodName(object sender, Android.Text.TextChangedEventArgs e) { ... }
Text Editor Action Event - applied to TextView objects
[InjectOnEditorAction(Resource.Id.some_text_view)]
void SomeMethodName(object sender, TextView.EditorActionEventArgs e) { ... }
If you would like Cheeseknife to support other Android view events you can edit Cheeseknife.cs
if you are using the source file version of the library, and add your own injection attributes. There are three steps to include a new event, for this example we will register the Scroll
event found on ListView
objects.
Each injection annotation is its own class. To make a new annotation, you just need to make a new class that has the same structure as the other Cheeseknife annotation classes. For our Scroll
event, add the following class to Cheeseknife.cs
:
[AttributeUsage(AttributeTargets.Method)]
public class InjectOnScroll : BaseInjectionAttribute {
public InjectOnScroll(int resourceId) : base(resourceId) { }
}
The annotation and string name of the Xamarin exposed event needs to be registered in the main Cheeseknife class so it can be found via reflection during injection:
static Dictionary<Type, string> GetInjectableEvents() {
var types = new Dictionary<Type, string>();
...
types.Add(typeof(InjectOnScroll), "Scroll");
return types;
}
By default if you don't actually reference an event in your code, the linker will strip it out during a release build which will make your app implode and probably make kittens cry somewhere.
To prevent this, we need to make a dummy reference (that will never actually be used) to preserve
the event we want to use.
[Preserve]
static void InjectionEventPreserver() {
...
new ListView(null).Scroll += (s, e) => {};
}
You can now use your new injection annotation in your Android app. For our example you could use the following code to inject the Scroll
event onto a ListView. Note that you need to match the signature of your custom method to be the same as if you had added the event manually via the Xamarin APIs ie object sender, AbsListView.ScrollEventArgs e)
:
public class ExampleActivity : Activity {
[InjectOnScroll(Resource.Id.list_view)]
void OnListViewScroll(object sender, AbsListView.ScrollEventArgs e) {
// This code will run when the list view scrolls ...
}
protected override void OnCreate(Bundle bundle) {
base.OnCreate(bundle);
SetContentView(Resource.Layout.main_activity);
Cheeseknife.Inject(this);
}
}
Copyright 2014 Marcel Braghetto
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.