Why is there so much casting in the Android code?
Almost half of my code constantly consists of casts like:
EditText yourEditText= (EditText) findViewById(R.id.yourEditText);
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
Is there some better way to approach this? (especially a way you can detect where you did go wrong before runtime)
Because if you want to assign a supertype to a specific subtype variable, you need to downcast it to the actual type. Much of the Android framework is from an era before Java generics was available.
Is there some better way to approach this?
If you only need the View from a findViewById() return, make the variable type a View.
You can also use generics to hide the casts. For example:
public <T extends View> T getView(#ResInt int resId) {
return (T) findViewById(resId);
}
This way you can assign like
EditText yourEditText = getView(R.id.yourEditText);
and the correct type is automatically inferred.
especially a way you can detect where you did go wrong before runtime
It's still runtime and you'll get ClassCastException if R.id.yourEditText is in fact not an EditText.
For getSystemService() there's already a method like that in API level 23. Implementing this pattern for older API levels is left as an exercise to the reader :)
It's mostly casting errors when you Replace in the layout file a LineairLayout -> RelativeLayout and forget to do it in the code. And suddenly everything breaks at run time while my editor does not show any errors and the code just compiles
Note that Android Lint will detect many errors like this and will report "Unexpected cast" errors.
Related
Android Studio shows the error
Unexpected implicit cast to CharSequence: layout tag was TextView
at this code
findViewById<TextView>(R.id.tv_name).text = "text"
If I write
(findViewById<TextView>(R.id.tv_name) as TextView).text = "text"
Everything is fine.
The question is why this happens? Doesn't findViewById<TextView> already have type TextView?
You can use Kotlin Android Extensions
Kotlin Android Extensions are Kotlin plugin that will allow to recover views from Activities, Fragments and Views in an amazing seamless way.
you can directly use
tv_name.text = "text"
no need of findViewById
Reference
https://antonioleiva.com/kotlin-android-extensions/
You can directly use all the views using DataBinding. Less code and very advance feature of android. There are many articles for Android DataBinding.
https://developer.android.com/topic/libraries/data-binding/index.html
This is only warning of lint. You can ignore it with #SuppressLint("WrongViewCast"). This happend because of usage of generic types from Java in Kotlin.
First of, Kotlin under the hood, just wraps up java findViewById method. Up to API 26 explicit cast was neccessary as method returned View, but now it returns . Check this answer No need to cast the result of findViewById?
If you dive into the source code, through invokations of findViewById, you'll get to Window.findViewById method, and if you look to description of it in documentation, you'll see one note there, which says that: "In most cases -- depending on compiler support -- the resulting view is automatically cast to the target class type. If the target class type is unconstrained, an explicit cast may be necessary." https://developer.android.com/reference/android/view/Window.html#findViewById(int)
I don't know what "unconstrained" actually means in this context, but as i understand, in some cases cast is required in others is not, so just deal with it. For example, i tried to add some param to ImageView and compiler didn't show any kind of warnings:
findViewById<ImageView>(R.id.iv).adjustViewBounds = true
I'm using UIAutomator test framework for long tests (concerning to my acceptance test). And I need to wait until some activity is started.
I decided to use By.clazz (android.support.test.uiautomator package) methods to find activity object. I expected that something like
uiDevice.wait(Until.findObject(By.clazz(SomeActivity.class)), 30000);
will work. But it doesn't. I suppose that object of my activity cannot be found. I tried to use other By.clazz methods with different params but without success.
So, my code is pretty simple:
UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
/*.... do something...
like click on buttons which will open some activities...
*/
//does not work, time value just for sample
uiDevice.wait(Until.findObject(By.clazz(SomeActivity.class)), 30000);
I found workaround solution with using By.res, like
uiDevice.wait(Until.findObject(By.res(BASIC_PACKAGE, "someMainIdInSomeFragment")), 30000);
But I have very complicated structure of the app with base activities and so on. I often have the same layout for different activities with load different fragments. So I need to know that we started exactly SomeActivity ,regardless of loaded fragments.
So, the questions are:
Is it possible to use By.clazz for Activity to find its object?
Is there some another way to find activity object with UIAutomator?
Did I do everything right? Or maybe there are some mistakes? Is it possÑ–ble to do with UiAutomator?
Thanks!
Using class with UiObject2
Find the EditText, make sure to click on it (legacySetText would do it implicitly), and set your text.
val input = By.clazz(EditText::class.java.canonicalName)
device.wait(Until.findObject(input), TIMEOUT).apply {
click()
text = "your_text"
}
Yes, could be done through the id.
// android:id="#+id/widget_id"
By.res(applicationId, "widget_id")
Your syntax seems good to me. Just make sure no spinner (or any other widget) is blocking your view during the click attempt.
Before Kotlin, Android developers supposed to save reference to the Activity's Views in a variable like this:
Button fooBtn = (Button) findViewById(R.id.btn_foo)
to reduce the amount of the boiler-plate code and the number of findViewById calls.
With the introduction of the Kotlin's Android Extensions we can reference the same Button by simply using:
btn_foo
Questions:
Does the btn_foo have a reference to the Button saved, or does it call findViewById every time?
Do developers still suppose to use variables to store btn_foo to improve app's performance, or just use it directly in the code?
Edit: there is an explanation how Extensions work, however it is still a bit unclear.
It's cached, so findViewById isn't called every time you need it. Storing the variable won't definitely improve the app's performance
One of the Kotlin Android Extension (KAE) developers Ihor Kucherenko confirmed that:
KAE will keep a reference to the view after the first call, instead of using findViewById all the time. That works only for Activities and Fragments.
KAE will not cache data and will use findViewById every time for any other element (except for an Activity/Fragment).
So in case you are going to init a ViewHolder:
class FooViewHolder(view: View): RecyclerView.ViewHolder(view) {
fun bind(day: FooItem.Day) {
btn_foo.text = day.title
}
}
Decompile into Java call will look like:
((Button)this.itemView.findViewById(R.id.btn_foo)).setText((CharSequence)day.getTitle());
which is exactly what you want to avoid.
The developers might be aware of this.
Conclusion: fill free to use KAE without any additional variables, but only for your Activitiies/Fragments.
Is there a way to make android studio perform automatic type casting for objects without the need to do that manually?
I don't think there's a way to do this automatically and IMHO this would be not that useful. There might be some cases where you don't want a cast (or a cast different to the one Android Studio suggests). If it would be done automatically you might not notice it it could lead to strange (i.e. unexpected) behaviour which is quite difficult to detect later.
What Android Studio can do for you: It can give you a warning and suggest a cast. For example, when you do a findViewById() call and want to assign the result to a View object you do something like this:
Button btn = findViewById(R.id.button);
Android Studio will highlight the line because you need to cast the returned View object to a Button.
By moving the cursor to the line and pressing Alt+Enter you can select the option "Add cast.." and it will insert the cast for you:
Button btn = (Button) findViewById(R.id.button);
Is it somehow possible that instead of:
Button btnNextWord = (Button) this.findViewById(R.id.btnNextWord);
Eclipse automatically generates for me something like:
Button btnNextWord = this.btnNextWord;
or
Button btnNextWord = R.id.getBtnNextWord(this);
??
No, because what you're doing in requesting a reference to a child element that has a certain name and Eclipse isn't actually loading in the layout xml so it can't know what is in it.
I know this is a very late response but it might help someone who read.
You can use the android annotations library for doing something similar to what you want.
As you can see on their page, you can write a field in the code like this:
#ViewById
ListView bookmarkList;
The library will findById an item with id R.id.bookmarkList and cast it to ListView.
Or you can use the annotation like this
#ViewById(R.id.myTextView)
TextView someName;
if you want to give a better name to the field.
DISCLAIMER:
I never used the library so I'm not sure whether you can use just that annotation or you're bound to use their whole set of annotations.