I migrated recently from BrowseFragment to BrowseSupportFragment in Kotlin for an Android TV app.
In the onActivityCreated I set some properties which rely on getting the color. To get the colors I use:
ContextCompat.getColor(context, R.color.fastlane_background);
The issue here is that context is nullable and getColor doesn't accept that.
Every time I need the context, do I need to do something like this:
val ctx = context ?: return
ContextCompat.getColor(ctx, R.color.fastlane_background);
Is this the recommended solution, are there better ways?
Use requireContext() to get a non-null Context associated with your Fragment.
Related
I am trying to use a code snippet from the official android documentation (https://developer.android.com/training/printing/photos#kotlin) which is the doPhotoPrint() function in the code that I have attached, in order to learn how to use the PrintHelper class in Kotlin and Android Studio. See the attached image of the the code snippet:
The problem is that when I put the code in Main Activity in my test app, it keeps showing "activity?." as red. Why is this the case and how can I get the code to work so that is provides the user with the option to print? Thanks
The code you linked is just a general how to use a library function snippet - it's not put in any context, but we can assume it's probably written with a Fragment in mind. Fragments have a getActivity() method that returns the Activity it's currently attached to (or null if it's not)
Kotlin allows you to access getter and setter functions from Java code as if they were properties - so basically, instead of having to do value = getThing()
and setThing(newValue) you can treat it like a variable: value = thing, thing = newValue etc. So when you access the activity property in this code, it's really calling getActivity(). And because the result can be null, you use the ? to null-check it before trying to do anything with it.
Really what that snippet is saying is, this code needs access to a Context, and it's using an Activity as one in the example. If you have a Fragment, you can just drop this code right in. If you're in an Activity though, then you obviously don't need to get one - this is your Activity! And you don't need to null-check it either of course. (And there's no activity property or getActivity() method to access, which is why it's showing up red and unrecognised)
So you can just replace that stuff and the checking code around it with:
private fun doPhotoPrint() {
// using 'this' to pass the current activity as the context
val printHelper = PrintHelper(this).apply {
scaleMode = PrintHelper.SCALE_MODE_FIT
}
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.droids)
printHelper.printBitmap("droids.jpg - test print", bitmap)
}
It is showing red because you have not declared the nullable object activity anyway before you use it
I use findViewById to get references to views in a custom DialogClass outside the view hierarchy. Since I want these references throughout the dialog class, and since they are not available until the call to setContentView, I use lazy field initialization in the dialog class definition:
private val balanceView : TextView? by lazy { dialog?.findViewById(R.id.balanceTextView)}
Later, in a separate function that runs after the call to setContentView, I have two commands related to balanceView:
private fun setupBalance(){
balanceView!!.visibility = View.VISIBLE
balanceView.text = presentation.balance
}
I expected the second command to compile since we are ensuring balanceView is not null in the first command. However, the compiler underlines the second command with this message:
Smart cast to 'TextView' is impossible, because 'balanceView' is a property that has open or custom getter
I was wondering how to interpret this message - to my knowledge the lazy keyword doesn't make a variable open.
The use of a property delegate like lazy means it has a custom getter. A custom getter prevents the compiler from being able to ensure that the value returned by the property will be the same in your two consecutive calls, so it cannot smart-cast.
Since balanceView is nullable, you shouldn't be using !! either, or your app will crash when balanceView is null. The correct way to write your function (if you don't know if balanceView is currently null) would be like this:
private fun setupBalance(){
balanceView?.apply {
visibility = View.VISIBLE
text = presentation.balance
}
}
If there's no chance of it being null, don't use a nullable property, and then you won't have to worry about null-safe calls and smart-casting. But you seem to also have a nullable dialog that it is dependent on. I don't know what's going on higher-up-stream, so I can't help with that.
In an existing Android App project (MVVM is used as pattern) I have found a PagerAdapter to swipe several information within this adapter:
class InformationSlideAdapter(ctx: Context) : PagerAdapter() {
private var contextCopy = ctx
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val layoutInflater = contextCopy.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val layoutScreen = layoutInflater.inflate(R.layout.slide_item, null)
val imageView = layoutScreen.findViewById<ImageView>(R.id.iv_logo)
//..
}
}
Question 1: Why is there findViewById() used? Shouldn't databinding solve this issue also for PageAdapters?
Question 2: Whenever I find a context in any other class than my view (especially when MVVM was used), this is very anti pattern for me. Why was a context provided there? Is there a reason for not using
val imageView = container.findViewById<ImageView>(R.id.iv_logo)
without inflating the two lines ahead?
Question 3: Altough the code is working (for now).. How are copies handled in kotlin?
private var contextCopy = ctx
Here a complete new copy instance is created in Kotlin? E.g. when I flip the screen, the context in corresponding InformationSlideActivity handles this correctly, but my InformationSlideAdapter still has an old instance of context with the unflipped state?
Unfortunately I cant ask the coder, since he has gone.
Thnx in advance
Pav
Question 1:
databinding or synthetics could be used use instead of findViewById. This could be legacy code or given the id of the view, it is possible that there are more views with same id and the developer wanted to avoid ambiguity by searching in a specific layout.
Question 2:
Injecting or passing context (in most cases) is an antipattern. If the context is needed it can be retrieved from the container.
the difference between
layoutScreen.findViewById<ImageView>(R.id.iv_logo)
and
container.findViewById<ImageView>(R.id.iv_logo)
is that the first one is searching in the newly inflated layout, that may or may not be attached to the container at that point in time.
Question 3:
private var contextCopy = ctx
this makes no sense, it is just making a reference copy. Given that the context is passed as a contructor parameter it is possible that the developer didn't know that they could do the following:
class InformationSlideAdapter(val ctx: Context, list: List<RelevantItem>) : PagerAdapter() {
in either case the context will not be refreshed and to have a new instance the adapter should be recreated
As per the answer of question2, the context should not be passed to the adapter and instead retrieved from the container.
I have a check box in fragment and trying to set text color on it using ContextCompat.getColor
the code
optionCb.setTextColor(ContextCompat.getColor(activity,android.R.color.white));
It shows error
required : Context
Found : fragmentactivity
Even used
optionCb.setTextColor(ContextCompat.getColor(activity.applicationContext,android.R.color.white));
Still shows error
What should be the context object here?
was able to solve by using requireActivity()
optionCb.setTextColor(ContextCompat.getColor(requireActivity(), android.R.color.black));
Try to use requiredActivity or requiredContext instead of activity
The ContextCompat.getColor() accepts two arguments -- the first of which is a non-null Context object.
If you're writing code in Kotlin, Android Studio is likely complaining about the Context object you're passing to getColor() being nullable. The context and activity parameters available to Fragments are nullable in Android.
As others have already mentioned, you can use the requireContext() function. However, while this will satisfy Android Studio, it should be used with caution since it will throw an IllegalStateException if the Fragment's context is null (the context of a Fragment is not always available).
My recommendation would be to set the text color in your xml layout file if at all possible. If you have to do it programmatically, the safest way is to handle the null case:
context
?.let { ContextCompat.getColor(it, android.R.color.white) }
?.also { optionsCb.setTextColor(it) }
optionCb.setTextColor(ContextCompat.getColor(getContext(),android.R.color.white));
I have TextView for showing time. I want to use Android's DataBinding plugin.
For formatting time I am using DateUtils.formatDateTime(context, int, int) method which takes Context instance. Is it possible to get context include element? Or do I have to use old school way?
Thanks
Also you can do something like this in your view using the current view context as parameter.
...
android:text="#{yourModelHere.yourModelMethodHere(context)}"
...
Thought I should answer instead of putting in a comment. You'll have more options when rc2 is released. In rc1, you can pass the context in a variable to the Binding, then pass it as a parameter to the method. Alternatively, you can create a custom attribute for data binding:
#BindingAdapter({"timeMillis", "dateFlags"})
public static void setDateText(TextView view, int timeMillis, int dateFlags) {
view.setText(DateUtils.formatDateTime(view.getContext(), timeMillis,
dateFlags));
}
And then use it in your TextView:
<TextView ... app:timeMillis="#{timeVar}" app:dateFlags="#{dateFlags}"/>
A special variable named context is generated for use in binding
expressions as needed. The value for context is the Context from the
root View's getContext(). The context variable will be overridden by
an explicit variable declaration with that name.
In other words, every time you need to pass the context just use "context" as in #{Object.method(context)}.
To used string resources use this
this.yourView.getRoot().getResources().getString(R.string.your_string)