Setting Snacksbar message from strings.xml - android

I am trying to set the Snacksbar message and action texts with values from strings.xml. If I call .toString() on the values it will obviously be set to some random numbers, as expected. I can't get a reference to context, because it isn't a composable function, so I can't use LocalContext.current meaning I cannot access .getString(). How do I set the value of the message and action properly?
fun onEvent(event: TaskListEvent) {
when (event) {
is TaskListEvent.OnDeleteTask -> {
viewModelScope.launch(Dispatchers.IO) {
deletedTask = event.task
repository.deleteTask(event.task)
sendUiEvent(
UiEvent.ShowSnackbar(
message = "Task successfully deleted!", action = "Undo"
)
)
}
}

It looks like you need an Application class so you can get context when you need it. You have to set the android:name in the application tag in the manifest to the Application class that you use
Something like,
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
instance = this
}
val context: Context
get() = this
companion object {
var instance: MyApplication? = null
private set
}
}
the Usage for this class is
MyApplication.instance
MyApplication.instance?.context

Related

class property becomes null after viewModel calls it in kotlin

class MainAcitvity
fun roomSetup(){
setFavouriteDao = FavouriteDatabase.getDatabase(applicationContext).setFavouriteDao()
repositoryRoom = LorRepository(setFavouriteDao)
viewModelRoom = ViewModelProvider(this,LorViewModelFactory(repositoryRoom!!)).get(LorViewModel::class.java)
}
override fun onMovieClick(position: Int) {
roomSetup()
Toast.makeText(this#MainActivity, "clicked!"+position, Toast.LENGTH_SHORT).show()
var setFavourite = SetFavourite(movieResponse!!.docs[position].Id.toString(),movieResponse!!.docs[position].name.toString())
viewModelRoom.addToFavourites(setFavourite)
}
class ViewModel
fun addToFavourites(setFavourite: SetFavourite){
viewModelScope.launch(Dispatchers.IO){
lorRepository.addToFavourites(setFavourite)
}
}
class LorRepository( favouriteDao: SetFavouriteDao?) {
var favouriteDao : SetFavouriteDao
init {
this.favouriteDao = favouriteDao!!
}
private var lorApi: LORApi.LorCalls? = null
constructor(lorApi2 : LORApi.LorCalls?, favouriteDao: SetFavouriteDao?) : this(favouriteDao){
this.lorApi = lorApi2
}
I have 2 constructors
one to initialize room other for initializing retrofit
I am Also doubtful about the constructor in Repository. Thoose are made for 2 different purposes, one for initializing room database and other for repository. but everytime I create one object of room/retrofit the second constructor , when called, fills it with null values
My questions for you are:
Why do you to initialize retrofit and room's dao in a separate constructor?
What is it that you try to achieve?
In your code you only call to initialize dao constructor, therefore lorApi is null.
For your case you wouldn't want to initialize them separately.
Change your code to this:
class LorRepository(private val lorApi : LORApi.LorCalls, private val favouriteDao: SetFavouriteDao)

How to programically trigger notify on MutableLiveData change

I have a LiveData property for login form state like this
private val _authFormState = MutableLiveData<AuthFormState>(AuthFormState())
val authFormState: LiveData<AuthFormState>
get() =_authFormState
The AuthFormState data class has child data objects for each field
data class AuthFormState (
var email: FieldState = FieldState(),
var password: FieldState = FieldState()
)
and the FieldState class looks like so
data class FieldState(
var error: Int? = null,
var isValid: Boolean = false
)
When user types in some value into a field the respective FieldState object gets updated and assigned to the parent AuthFormState object
fun validateEmail(text: String) {
_authFormState.value!!.email = //validation result
}
The problem is that the authFormState observer is not notified in this case.
Is it possible to trigger the notification programically?
Maybe you can do:
fun validateEmail(text: String) {
val newO = _authFormState.value!!
newO.email = //validation result
_authFormState.setValue(newO)
}
You have to set the value to itself, like this: _authFormState.value = _authFormState.value to trigger the refresh. You could write an extension method to make this cleaner:
fun <T> MutableLiveData<T>.notifyValueModified() {
value = value
}
For such a simple data class, I would recommend immutability to avoid issues like this altogether (replaces all those vars with vals). Replace validateEmail() with something like this:
fun validateEmail(email: String) = //some modified version of email
When validating fields, you can construct a new data object and set it to the live data.
fun validateFields() = _authFormState.value?.let {
_authFormState.value = AuthFormState(
validateEmail(it.email),
validatePassword(it.password)
)
}

How to throw am Exception in Kotlin custom getter and stop the app from running?

I want to verify that my MediatorLiveData has an actual value set:
val entries = MediatorLiveData<List<Entry>>()
get() = when {
entries.value == null -> throw IllegalStateException("wah")
else -> field
}
I get no compiler errors, but when running the code I get an StackOverflow, because the getter is called over and over again in the (entries.value == null) path.
1. How to throw an exception in a custom getter and stop the app from running
UPDATE WITH FINAL SOLUTION:
Thanks to #zapl and #kcoppock for your answers. Both of them helped me to get to my final solution:
private lateinit var _entries: LiveData<List<Entry>>
val entries = MediatorLiveData<List<Entry>>()
get() = when {
!::_entries.isInitialized -> throw IllegalStateException("EntryListViewModel was not initialized. Please call init() first.")
else -> field
}
fun init() {
_entries = getEntries(false)
entries.addSource(_entries) { entries.value = it.orEmpty() }
}
fun refreshEntries() {
entries.removeSource(_entries)
_entries = getEntries(true)
entries.addSource(_entries) { entries.value = it.orEmpty() }
}
I also learned from another source about .isInitialized for lateinit vars which can be used for exactly what I needed. Also the graceful fallback to an empty list was good idea.
What I would do is keep the LiveData private and surface a separate accessor for the checked property:
private val _entries = MediatorLiveData<List<Entry>>()
val entries: List<Entry>
get() = _entries.value!!
Better yet, unless you explicitly want to crash in this case, you could just return an empty list instead:
private val _entries = MediatorLiveData<List<Entry>>()
val entries: List<Entry>
get() = _entries.value.orEmpty()
That said, the point of LiveData is to use an observer, so you only get an update when a value is posted.
EDIT: If your goal is to force an initial value, you could create a subclass that enforces this:
class NonNullMediatorLiveData<T>(initialValue: T) : MediatorLiveData<T>() {
init { value = initialValue }
override fun getValue(): T = super.getValue()!!
override fun setValue(value: T) {
// assert since the parent is defined in Java
super.setValue(value!!)
}
}

Kotlin set value of global variable

We are trying to set the value of a global variable declared in the code below
class MyApplication: Application() {
var globalVar = 2
}
Now we have a Main Activity that has a Edit Text named etPerson we would like to enter a value in etPerson and set the entered value equal to globalVar
Why because we want to make a call to a database function in another Activity
Here is the code that makes the call to the DB
var mApp = MyApplication()
var intGlobalVar = mApp.globalVar
println("############################ globalINT = "+intGlobalVar)
var ITEM = db.getItem(intGlobalVar)
println("%%%%%%%%%%%%%%%%%% ITEM "+ITEM?.id+" name "+ITEM?.name)
And for clarity here is the DB function
fun getItem(id: Int): Contact? {
val db = this.writableDatabase
val selectQuery = "SELECT * FROM $TABLE_NAME WHERE $colId = ?"
db.rawQuery(selectQuery, arrayOf(id.toString())).use {
// .use requires API 16
if (it.moveToFirst()) {
val result = Contact(0)
result.id = it.getInt(it.getColumnIndex(colId))
result.name = it.getString(it.getColumnIndex(colName))
return result
}
}
return null
}
So the issue is setting the var globalVar to the value entered in etPerson on the Main Activity
The concept can be accomplished using put and get with intents but that is not our goal here
Our question is how to set the globalVar to the value entered in the Edit Text?
When your app starts one and only one object of the class MyApplication() will be created, so you don't need:
var mApp = MyApplication()
You can access MyApplication() and all its members from everywhere in your app.
Declare a companion object in MyApplication() and put globalVar's declaration inside:
class MyApplication: Application() {
companion object {
var globalVar = 2
}
override fun onCreate() {
super.onCreate()
// initialization code here
}
}
So in your MainActivity class or elsewhere you can use MyApplication.Companion.globalVar to get or set its value.
Or you can import the globalVar variable in any class like:
import yourpackagename.MyApplication.Companion.globalVar
and refer to it simply globalVar
You also need to declare MyApplication in the manifest:
<application
android:name=".MyApplication"
I use this way to send an Intent :
Target activity or class or object (Reciver):
class ReturnActivityIntent private constructor() {
var data: Intent? = null
companion object {
val instance = ReturnActivityIntent()
}
}
In the MainActivity (Sender)
val returnActivityIntent: Notifications.ReturnActivityIntent = Notifications.ReturnActivityIntent.instance
returnActivityIntent.data = Intent(this, MainActivity2::class.java)
so you can change or share variables in this way

How to delay to assign a value to a val in kotlin?

I define mContext as val and I need to assign a value to it in the fun onCreate.
The code private lateinit val mContext: Context isn't correct, how can I do?
class UIMain : AppCompatActivity() {
private val mContext: Context
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.layout_main)
mContext = this
}
}
Answer Strelok
The keyword this isn't always fit, just like the following code, so I think it's more handier to assign this to mContext.
private Context mContext;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_main);
mContext=this;
findViewById(R.id.btnClose).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(getApplicationContext(), "Hello A", Toast.LENGTH_LONG).show();
Toast.makeText(mContext, "Hello B", Toast.LENGTH_LONG).show();
//Toast.makeText(this, "Hello C", Toast.LENGTH_LONG).show(); //Doesn't work
finish();
}
});
}
If you're using the lateinit keyword, you'll have to change from val to var, thus losing the immutability. If that's ok for you, Strelok's answer will suffice.
But if you really need a val on your code for any reason, you can try the lazy delagate property.
As stated on the Android Essense blog:
This property takes in a lambda, which is executed the first time the
property is accessed. After that, it will return the value that was
assigned to it. This way we can declare the property as immutable, and
non-null, so that as long as the fragment (or Activity) is created before we access it the first time.
For example, in your case you could try to do this:
private val mContext : Context by lazy {
this
}
In short:
If your value can or need to be mutable, use lateinit
If your value is meant to be initialized once, and shared across your methods, use lazy with val.
But as stated by the others, in your specific case it's better to just call this when you need the Activity/Context reference.
Edit: As per your example on why you would need a mContext inside your Activity, I still say you don't need it.
Instead of trying to call this and use it on the Toast#makeText() inside your anonymous function directly, you could either:
Change this to UIMain.this.
Create a method inside your Activity, and call that method inside the anonymous function. e.g.: findViewById(R.id.btnClose).setOnClickListener { otherMethod() }, and inside that method you can reference the Activity using this again.
I think what you really want is a qualified this expression like
this#UIMain
as in
Toast.makeText(this#UIMain, "Hello C", Toast.LENGTH_LONG).show(); //works everytime
that solves all your issues. See Kotlin this expression
PS: If that solves your problem you should rename the question to "how to use outer this in nested object"
I agree with the person commenting, it's rather strange why you want to keep a reference to this in a private property (potential memory leak).
But, in any case, Kotlin has a lateinit modifier that let's you delay setting a property value, but it must be set before the property is used for the first time.
class UIMain : AppCompatActivity() {
private lateinit var mContext: Context
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.layout_main)
mContext = this
}
}
We have never liked Toast even with jelly Here is a way to replace that little slice of toast with a custom visible error message. Yes we included the toast since the question is about Toast. To use this concept you need to add a TextView some where on your screen. This code is testing for valid entry in two EditText fields.
fun onLAMmultiply(view: View ){
if(etValOne.text.length == 0){
error("Enter First Value")
toast("Enter First Value TOAST")
etValOne.requestFocus()
return#onLAMmultiply
}
if(etValTwo.text.length == 0){
error("Enter Second Value")
toast("Enter Second Value TOAST")
etValTwo.requestFocus()
return#onLAMmultiply
}
var X = etValOne.text.toString()
var Y = etValTwo.text.toString()
val multB = {X:Double,Y:Double -> X.times(Y)}
val df = DecimalFormat("0.00")
//val df = DecimalFormat("#.##")
df.roundingMode = RoundingMode.CEILING
df.format(multB(X.toDouble(),Y.toDouble()))
etANS.setText(df.format(multB(X.toDouble(),Y.toDouble())).toString())
etANS.setText(multB(X.toDouble(),Y.toDouble()).toString())
}
fun Context.toast(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
fun error(msg:String){
object : CountDownTimer(4000, 1000) {
override fun onTick(millisUntilFinished: Long) {
tvError.visibility = View.VISIBLE
tvError.setText(msg)
}
override fun onFinish() {
tvError.visibility = View.INVISIBLE
tvError.setText("")
}
}.start()
}

Categories

Resources