What does "activity?." mean in Kotlin? - android

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

Related

How does this android code get the activity from ? material-components-android-codelabs

material-components-android-codelabs is a git repo of demo code for material components. In the 102-starter branch of this repo, in LoginFragment.kt, there's this bit of code
(actvity as NavigationHost).navigateTo(ProductGridFragment(),false)
In the import statements, nothing in there seems to indicate where activity comes from and using android studio to find any declaration goes to a function with the signature public final FragmentActivity getActivity(). How is activity set and brought into the scope of the fragment?
Kotlin allows you to get and set properties directly without calling the method. E.g. instead of calling getActivity, you can just use activity. So (actvity as NavigationHost) actually translates to (getActivity() as NavigationHost)
Check the Kotlin reference here. Quote from the linked documentation:
To use a property, simply refer to it by its name
getActivity() is a method on Fragment instances, and Kotlin allows you to access Java-style setters and getters as though they're properties:
// getters
val newThing = stuff.getThing()
val newThing = stuff.thing
// setters
stuff.setThing(newThing)
stuff.thing = newThing
// boolean setters
dog.setGood(true)
dog.isGood = true
// boolean getters
val goodBoy = dog.isGood()
val goodBoy = dog.isGood
note that the property versions look the same whether you're getting or setting, you're just reading and writing to a single "property". Under the hood, it's making the call to the relevant function.
Fragment has a function: final public FragmentActivity getActivity()
-> since you're in the scope of a fragment, and the function is in the fragment scope, too, you don't need any import
To be able to use this activity reference, your fragment needs to be attached to an activity. => This should happen somewhere around onAttatch().
Last tip: when you're in a lifecycle where you're sure you have a backing activity, you can use requireActivity() to avoid unnecessary null-checks

Android Kotlin: Smart cast to 'TextView' is impossible, because 'x' is a property that has open or custom getter

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.

How to use a ViewModel to save a bitmap correctly?

In the official android documentation, on how to implement a ViewModel, there is this method:
private void loadUsers() {
// Do an asynchronous operation to fetch users.
}
In my case I don't need to fetch any data, I just want to save a bitmap which in another class than the ActivityClass.
I tried making a setter method, but I can't make an object of the ViewModel class! <(Look at my edit)>
This is my ViewModel class:
public class MyViewModel extends ViewModel {
private MutableLiveData<Bitmap> bitmapMutableLiveData;
public LiveData<Bitmap> getBitmapMutableLiveData() {
if (bitmapMutableLiveData == null) {
bitmapMutableLiveData = new MutableLiveData<Bitmap>();
}
return bitmapMutableLiveData;
}
public void setBitmap(Bitmap bitmapImage) {
bitmapMutableLiveData.postValue(bitmapImage);
}
}
And this is where I get and set the bitmap:
void loadScaledImage(Uri photoUri){
mUpdateGraphicViews.onClear();
if(photoUri != null){
bitmapImage = decodeSampledBitmapFromUri(photoUri);
mImageView.setImageBitmap(bitmapImage);
mFTR.recognizeTextFromImage(getScaledBitmap(bitmapImage));
}
}
So, how can I save a bitmap in the right way using a ViewModel? Thanks!
EDIT:
I made mistake by making the ViewModel class outside of the package, but I fixed that and I called the setter method like that.. in the loadScaledImage() method that I mentioned earlier.
myViewModel.setBitmap(bitmapImage);
But, now I faced a new problem in the onCreate() method!!!!
MyViewModel model = new ViewModelProvider(this).get(MyViewModel.class); //Cannot resolve constructor ViewModelProvider(com.ziad.sayit.PreviewActivity)
model.getBitmapMutableLiveData().observe(this, bitmapMutableLiveData -> { //Lambda expressions are not supported at language level 7
// update UI
});
I have noticed that the ViewModelProvider() constructor takes two parameters in the super-class but this is now the case in the documentation, and the second one is weird, I have java 8 or is that not the case?
Update:
Using those two answers that I found, I was able to fix my last problem:
Cannot resolve ViewModelProvider construction in a fragment?
Java "lambda expressions not supported at this language level"
So, now, This is how I set and get the bitmap:
Made a ViewModel object as a global variable:
Bitmap savedImage;
MyViewModel model;
And this is in onCreate():
model = new ViewModelProvider(this).get(MyViewModel.class);
model.getBitmapMutableLiveData().observe(this, bitmapMutableLiveData -> {
savedImage = bitmapMutableLiveData;
});
And then this is how I set the bitmap
if(savedImage != null) {
helper.loadSavedBitmap(savedImage); //load the saved image
}else{
helper.loadScaledImage(imageUri); //there is a bunch of code before this but I delete it for the simplicity
model.setBitmap(helper.bitmapImage); //set that new Image
}
But then I tested the app and nothing changed!
Update 2:
So, I discovered that the ViewModel doesn't work like normal code. the problem in the code above is that I used it ignoring the lifeCycle of the ViewModel and its Terms (because I didn't know any of that), but I have noticed that (And if I said anything wrong please correct me):
The ViewModel doesn't run in the order of the code, however, it
runs after everything in onCreate(). This is why you can't depend
on it to save a value to a global variable like I did.
If the ViewModel has no value stored on it, It doesn't work at
all! So, no need to check if there is a value in it or not.
It is called everytime onResume() or onStart() is called -I'm
not really sure which one- regardless of the fact if onCreate() is
being called or not..
So, I refactored the code, but still, there is something that I'm missing! All the code works fine even the ViewModel, but when I change the device language to check if it succeed to use the saved image.. an error occurs!!
I'm sharing my code with you:
PreviewActivity (where I use the ViewModel)
Helper class (prepare the image and set it to the screen)
MainActivity (Lanches the PreviewActivity)
LogCat Error
You simply can't make a ViewModel object in any another simple java class rather than Activity class because its sole purpose is to be aware of activity life cycle.
Although instead of directly storing bitmap from your simple java class i would recommend to get instance of your simple java class in activity class in which your viewModel is initialised, from there you can store your bitmap in viewModel.

Which one should I use between it and this in kotlin when I use extensions class?

ScreenDef is a class, I add a function setDevice for the class, which one is correct between Code A and Code B? why?
I think that Code B is correct, right?
Code C
data class ScreenDef(
val brightness: Int
): DeviceDef
class ScreenHelper(val mContext: Context) {
fun setScreen(aScreenDef: ScreenDef){
}
}
Code A
fun ScreenDef.setDevice(mContext: Context) {
ScreenHelper(mContext).setScreen(this)
}
Code B
fun ScreenDef.setDevice(mContext: Context) {
ScreenHelper(mContext).setScreen(it)
}
You should use this. it is referred as shorthand if there is only one parameter in lambdas.
context?.let {
it.resources.getInt(R.int.anyint) // just for example
}
In above snippet, it is the shorthand for lamda parameter(in case of only one parameter).
context?.let { cxt -> // here we have manually defined a parameter
cxt.resources.getInt(R.int.anyint) // just for an example
}
In this snippet, instead of it we have created cxt that is exactly same as it.
Actually you are taking the concept of Extension function wrong.
You are creating a data class ScreenDef and want to create an extension function to it, why? If you really want to have a member function just create a normal class and have a function in it.
Extension function should be created when target class is not maintained by you. For example: Activity, Fragments are not maintained by you and if you want to add a custom function, you have to extend them and do it. So to prevent it extension function comes into picture and they are really handy that's why we love it.
You can rather argue, whats wrong with creating extension function for a class created by us. It may or might not be true. It actually depends.
Let's take an example, suppose we have developed a library to draw simple symbols on canvas and there are several function we have created. It turned out to be so good that people are using it, we decided to created advanced version, that can draw more complex symbols that requires using our already developed simple lib. So when we extend the classes of simple lib we might need some functionality to improve some thing etc. in that case if we have imported our simple lib as dependency then its good to create extension function otherwise we would have to create one more child of that class and create desired function. If we have import our lib as source code, we can just go to the source fine and create a function inside it.
I hope it helps.

How to cast view elements in kotlin? [duplicate]

I'm trying to build android application using Kotlin for the first time.
I want to declare on some buttons outside the OnCreate method and i can initialize them only Inside this function with findViewById.
Can i declare in simple and clean code like in java?
private Button btnProceed;
Because when converting it to Kotlin it look like:
private var btnProceed: Button? = null
And then when initialize OnClick function need to add ! sign:
btnProceed!!.setOnClickListener
What is the right and cleanest way?
This is a good use case for lateinit. Marking a property lateinit allows you to make it non nullable, but not assign it a value at the time that your Activity's constructor is called. It's there precisely for classes like Activities, when initialization happens in a separate initializer method, later than the constructor being run (in this case, onCreate).
private lateinit var btnProceed: Button
If the property is read before a real value is assigned to it, it will throw an exception at runtime - by using lateinit, you're taking the responsibility for initializing it before you access it for the first time.
Otherwise, if you want the compiler to guarantee safe access for you, you can make the Button nullable as the converter does by default. Instead of the unsafe !! operator though, which the converter often uses, you should use the safe call operator where you access the property:
btnProceed?.setOnClickListener { ... }
This will make a regular call if btnProceed is a non-null value, and do nothing otherwise.
On a final note, you can check out Kotlin Android Extensions, which eliminates the need to create properties for your Views altogether, if it works for your project.
Last edit (for now): you should also look at using lazy as described in the other answers. Being lazy is cool.
Instead of using lateinit, you can also do lazy initialization:
private val button by lazy {
findViewById(R.id.button) as Button
}
The first time you access the button property, it will execute the block once and use the result for future calls. In onCreate for example, you can now directly access it:
fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(bundle)
setContentView(R.layout.my_view)
button.setOnClickListener { ... }
}
You can do it with lateinit as #zsmb13 suggest BUT this has the disadvantage that your views will be variable instead of final. If you want them to be final you can use the lazy property delegation
By using lazy you can declare how the value will be initialized when you first try to access it so by declaring
private val btnProceed: Button by lazy {
findViewById(R.id.yourID)
}
Whenever you access your btnProceed you will have your activity (this example assume you're using an activity) loaded so you can use that method

Categories

Resources