lateinit property action has not been initialized in kotlin, how to fix it? - android

import android.app.Dialog
import android.app.TimePickerDialog
import android.os.Bundle
import android.text.format.DateFormat
import android.widget.TimePicker
import androidx.fragment.app.DialogFragment
import java.util.*
class TimePickerFragment : DialogFragment(), TimePickerDialog.OnTimeSetListener {
lateinit var action : (String) -> Unit
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
// Use the current time as the default values for the picker
val c = Calendar.getInstance()
val hour = c.get(Calendar.HOUR_OF_DAY)
val minute = c.get(Calendar.MINUTE)
// Create a new instance of TimePickerDialog and return it
return TimePickerDialog(activity, this, hour, minute, DateFormat.is24HourFormat(activity))
}
override fun onTimeSet(view: TimePicker, hourOfDay: Int, minute: Int) {
var min = minute.toString()
if(min.toInt() < 10) min = "0$min"
action("$hourOfDay:$min")
}
fun setListener(action: (String) -> Unit) {
this.action = action
}
}
my declare action was detected error in firebase inside fun setlistener this.action=action how to fix it? sorry i am newbie guys, i am need more references , thank you

The lateinit field holds a reference to the listener that does something with the result of the time that you've picked.
So if you show the picker dialog without setting that listener first, when the onTimeSet(..) function is called, which happens when you tap "OK" on the picker dialog, the action field still hasn't been initialised, so an UninitializedPropertyAccessException will be thrown.
You just need to set the action listener using the setListener(..) function beforehand. It can even be empty as shown below.
val dialog = TimePickerFragment()
dialog.setListener {
// your listener logic
}
dialog.show(supportFragmentManager, "fragment-tag")
Update: In onTimeSet, you are passing a string to action(..). In your code, action is a listener that does something with the string that you pass to it. But you are passing a String to the listener before it has been set to anything, that is why you are seeing an error.
lateinit fields must be set before they can be used.
The quickest modification to your existing class would be to apply a listener by default:
// Create a new instance of TimePickerDialog and return it
return TimePickerDialog(activity, this, hour, minute, DateFormat.is24HourFormat(activity)).apply {
action = { dateTimeString ->
Log.d("LOG_TAG", "Time Selected -> $dateTimeString") // this outputs the String defined in onTimeSet to Logcat
}
}
Then if you want to assign a different listener, you do so with setListener().

Just before the function that uses the variable or instance, initialise it to a reasonable value or use this if its part of the class constructors

Related

Stuck in Kotlin coding to do the Calendar page with a library in Android Studio

I want to do the calendar page in Kotlin which I imported library from hi-manshu/Kalendar. I want to do the calendar page in Month view. So, I use firey (from the document https://github.com/hi-manshu/Kalendar/blob/main/docs/Kalendar.md) to set it. I also want to do the other stuffs from the document such as Calendar header, Setting up Events, change Calendar color, etc. But I have no ideas how to do.
This is where I'm right now.
package com.example.calendar
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.himanshoe.kalendar.model.KalendarType
class MainActivity : AppCompatActivity() {
private lateinit var kalendarView: KalendarView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
kalendarView = findViewById(R.id.kalendarView)
// Customize the KalendarView with the Oceanic type
kalendarView.kalendarType = KalendarType.Firey
kalendarView.setSelectionMode(KalendarView.SelectionMode.SINGLE)
kalendarView.setStartDate("2022-01-01")
kalendarView.setEndDate("2022-12-31")
kalendarView.setOnDateSelectedListener { date ->
// Handle the date selection
}
}

Kotlin random() always generates the same "random" numbers

I have created an app which should choose an image randomly from an array of images. On my Emulator Nexus 5X Android 5.1 everything works as expected. As soon as I try the same on my real device Galaxy Note 10 Lite I always get the same "random" numbers in same order. I first need to restart my phone to generate a new list of "random" numbers which is then always the same. Example: My array contains 200 elements, I open the app on my Galaxy and it chooses the following random number for the image ids: 43, 12, 176, 33, 2, 78. Then I close the app and I open the app again, now it has the exact same "random" numbers again: 43, 12, 176, 33, 2, 78. I need to restart my phone to get new random numbers, which will stay the same until I restart my phone again. On my emulator everything works fine and I get new random numbers always when I restart the app as expected.
Here is my full code of my app without array list of images:
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val imageList = arrayOf(Image(R.drawable.image1, false),
Image(R.drawable.image2, false),
Image(R.drawable.image3, false))
val imageViewMain = findViewById<ImageView>(R.id.imageViewMain)
loadNextImage(imageViewMain, imageList)
imageViewMain.setOnClickListener {
val dialogClickListener =
DialogInterface.OnClickListener { _, which ->
when (which) {
DialogInterface.BUTTON_POSITIVE -> {
loadNextImage(imageViewMain, imageList)
}
DialogInterface.BUTTON_NEGATIVE -> { }
}
}
val builder: AlertDialog.Builder = AlertDialog.Builder(this)
builder.setMessage("Nächstes Bild?").setPositiveButton("Ja", dialogClickListener)
.setNegativeButton("Nein", dialogClickListener).show()
}
}
private fun getNextChoice(): Int {
return (0..1).random()
}
private fun getNextImage(imageList: Array<Image>): Int {
val listSize = imageList.size
var imageId: Int
do {
imageId = (0 until listSize).random()
} while (imageList[imageId].played)
imageList[imageId].played = true
return imageList[imageId].image
}
private fun loadNextImage(imageViewMain: ImageView, imageList: Array<Image>) {
val imageQuestionmark = R.drawable.questionmark
val nextChoice = getNextChoice()
if (nextChoice == 0) {
imageViewMain.load(imageQuestionmark)
} else if (nextChoice == 1) {
imageViewMain.load(getNextImage(imageList))
}
Toast.makeText(this, "Bild hat geladen", Toast.LENGTH_SHORT).show()
}
}
Image:
data class Image(
val image: Int,
var played: Boolean
)
EDIT:
I tried what cactustictacs suggested in the comment and create a simple app, once with the kotlin random function and once with the java random function. here is the code I used:
Kotlin:
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val buttonTest = findViewById<Button>(R.id.buttonTest)
buttonTest.setOnClickListener {
val getRandomNumber = (0..999).random()
Toast.makeText(this, getRandomNumber.toString(), Toast.LENGTH_SHORT).show()
}
}
}
Java:
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.Toast;
import java.util.Random;
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button buttonTest = (Button) findViewById(R.id.buttonTest);
buttonTest.setOnClickListener(v -> {
int randomNumber = new Random().nextInt(999);
Toast.makeText(this, "" + randomNumber, Toast.LENGTH_SHORT).show();
});
}
}
on Kotlin I get the same behavior as with my inital problem, doesnt matter what I do with the app (I CAN EVEN UNINSTALL AND INSTALL AGAIN) I always get the same set of numbers. On Java its working as exptected, as soon as I close the app I get a new set of numbers. So the error definetly lays in kotlin.
Maybe it helps, my Android version is 12 and my phone Galaxy Note 10 Lite.
I once had this issue too. My solution is to use a seeded random object instead of calling the function Random.nextSomething(). Then I seed currentTimeInMillis as the seed.
var randomGenerator = Random(System.currentTimeMillis())
var result = randomGenerator.nextInt(30, 50)
This is quite a terrible implementation of Kotlin defaul Random class. Java Random class tries a lot to always use a different seed on every new instance whereas Kotlin hardcoded the same seed through the whole device. How can this be a default behavior for a Random implementation. It took me quite a lot to understand it.
See Java implementation:
public Random() {
this(seedUniquifier() ^ System.nanoTime());
}
private static long seedUniquifier() {
// L'Ecuyer, "Tables of Linear Congruential Generators of
// Different Sizes and Good Lattice Structure", 1999
for (;;) {
long current = seedUniquifier.get();
long next = current * 181783497276652981L;
if (seedUniquifier.compareAndSet(current, next))
return next;
}
}
private static final AtomicLong seedUniquifier
= new AtomicLong(8682522807148012L);
Aaaaaand here comes Kotlin one:
companion object Default : Random(), Serializable {
private val defaultRandom: Random = defaultPlatformRandom()
private object Serialized : Serializable {
private const val serialVersionUID = 0L
private fun readResolve(): Any = Random
}
private fun writeReplace(): Any = Serialized
override fun nextBits(bitCount: Int): Int = defaultRandom.nextBits(bitCount)
override fun nextInt(): Int = defaultRandom.nextInt()
override fun nextInt(until: Int): Int = defaultRandom.nextInt(until)
override fun nextInt(from: Int, until: Int): Int = defaultRandom.nextInt(from, until)
defaultRandom is a singleton always initiates with same seed...
(I took this code through the Android Studio sources...)
Note: So it was a bug on kotlin version 1.7.10 and Android api less than 33-34 something. Fixed on 1.7.20...
It looks like you're calling loadNextImage() from activity's onCreate(). That means that unless the activity is destroyed, it'll never re-generate the random IDs that you want. What happens if you force-stop the activity, and then relaunch it? I would expect that you get a new set of IDs.
If I'm right, and you want a new set of IDs every time you open the activity, then the solution is to call loadNextImage() from onResume() (and if you don't want it generated every time the activity is resumed, you'll need to include some logic that decides when to regenerate those IDs)

findViewById error - Not enough information to infer type variable T. I copied java code and converted it online

Copied java code from diolor/swipecards (GITHUB). Converted it into kotlin with help pf online tools. It had some errors which were corrected but this last one still appears (findViewById) in the OnScroll function.
package com.example.fanatic
import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.widget.ArrayAdapter
import android.widget.Toast
import com.lorentzos.flingswipe.SwipeFlingAdapterView
class Swipe_card_activity : AppCompatActivity() {
private var al:ArrayList<String> = TODO()
private lateinit var arrayAdapter:ArrayAdapter<String>
private var i:Int = 0
override fun onCreate(savedInstanceState:Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_swipe_card_activity)
al = ArrayList()
al.add("php")
al.add("c")
al.add("python")
al.add("java")
al.add("html")
al.add("c++")
al.add("css")
al.add("javascript")
arrayAdapter = ArrayAdapter(this, R.layout.item, R.id.helloText, al)
val flingContainer : SwipeFlingAdapterView = findViewById<SwipeFlingAdapterView>(R.id.frame)
flingContainer.setAdapter(arrayAdapter)
flingContainer.setFlingListener(object: SwipeFlingAdapterView.onFlingListener {
override fun removeFirstObjectInAdapter() {
// this is the simplest way to delete an object from the Adapter (/AdapterView)
Log.d("LIST", "removed object!")
al.removeAt(0)
arrayAdapter.notifyDataSetChanged()
}
override fun onLeftCardExit(dataObject:Any) {
//Do something on the left!
//You also have access to the original object.
//If you want to use it just cast it (String) dataObject
makeToast(this#Swipe_card_activity, "Left!")
}
override fun onRightCardExit(dataObject:Any) {
makeToast(this#Swipe_card_activity, "Right!")
}
override fun onAdapterAboutToEmpty(itemsInAdapter:Int) {
// Ask for more data here
al.add("XML " + (i).toString())
arrayAdapter.notifyDataSetChanged()
Log.d("LIST", "notified")
i++
}
THE ERROR IS PRESENT HERE ON FINDVIEWBYID
IT SAYS : NOT ENOUGH INFORMATION TO INFER TYPE T.
**override fun onScroll(scrollProgressPercent:Float) {
strong textval viuw = flingContainer.selectedView
viuw.run {
findViewById(R.id.item_swipe_right_indicator).setAlpha(if (scrollProgressPercent < 0) -scrollProgressPercent else 0)
findViewById(R.id.item_swipe_left_indicator).setAlpha(if (scrollProgressPercent > 0) scrollProgressPercent else 0)**
}
}
})
// Optionally add an OnItemClickListener
flingContainer.setOnItemClickListener { itemPosition, dataObject -> makeToast(this#Swipe_card_activity, "Clicked!") }
}
fun makeToast(ctx: Context, s:String) {
Toast.makeText(ctx, s, Toast.LENGTH_SHORT).show()
}
#OnClick(R.id.right)
fun right() {
/**
* Trigger the right event manually.
*/
val flingContainer : SwipeFlingAdapterView = findViewById<SwipeFlingAdapterView>(R.id.frame)
flingContainer.getTopCardListener().selectRight()
}
#OnClick(R.id.left)
fun left() {
val flingContainer : SwipeFlingAdapterView = findViewById<SwipeFlingAdapterView>(R.id.frame)
flingContainer.getTopCardListener().selectLeft()
}
}
annotation class OnClick(val right: Int)
I believe the problem you are facing due to code conversion. Java doesn't require you to cast the view explicitly whereas Kotlin requires you to specify the type of view. You need to set the view within angular brackets like this
findViewById<View>(R.id.item_swipe_right_indicator).setAlpha(...)
The type you are getting with findViewById is potentially unconstrained so the type cannot be inferred and in Kotlin needs to be explicitly stated/casted.
If you are targeting API 26 or higher in your app you can do:
findViewById<THE_VIEW_TYPE>(R.id.item_swipe_right_indicator).setAlpha(...)
Otherwise for API 25 and lower you can do:
(findViewById(R.id.item_swipe_right_indicator) as THE_VIEW_TYPE).setAlpha(...)

Android Date Picker dialog context parameter

I am playing around with android and I wanted to create a date picker in my app. I followed the tutorial at developer.android.com which shows the following code:
class DatePickerFragment : DialogFragment(), DatePickerDialog.OnDateSetListener {
override fun onCreateDialog(savedInstanceState: Bundle): Dialog {
// Use the current date as the default date in the picker
val c = Calendar.getInstance()
val year = c.get(Calendar.YEAR)
val month = c.get(Calendar.MONTH)
val day = c.get(Calendar.DAY_OF_MONTH)
// Create a new instance of DatePickerDialog and return it
return DatePickerDialog(activity, this, year, month, day)
}
override fun onDateSet(view: DatePicker, year: Int, month: Int, day: Int) {
// Do something with the date chosen by the user
}
}
However, when I use this code android studio complains that there is a Type mismatch on the first parameter of DatePickerDialog:
Type mismatch.
Required: Contex
Found: FragmentActivity?
I also tried to get the context as activity?.applicationContext but that does not solve it either.
Is because FragmentActivity? is nullable ? so you need a non-nullable Context. You can use:
requireContext()
The function requireContext() is the recommended way of getting the context for non-nullable in Kotlin, in fragments. It doesn't mean is not nullable, the Context can always end up as null. What the function does is to throw an exception if is null, that is why you can use it as if weren't null. The exception is mostly never thrown and if it happens is because it was used before or after the Context were available, so using context!! would have also crashed.
Using activity!!.applicationContext would also work, but that is a cast to not nullable from a nullable and therefore not recommended.
This is very similar to requireActivity()
The last thing you could try is using this which would be the Fragment, sadly you can't
val context: Context = this
That would be not null because the fragment is not null if is doing something, but the fragment doesn't extend from Context like activities do (if you follow the inheritance of the context for the activities the 3 more upper parents are ContextThemeWrapper, ContextWrapper and Context)

CalendarView doesn't reset after getDate()

I am trying to get the numerical value of a date out of a CalendarView, which should be possible by using calendarView.date. I have attached this function to a button that displays the long calendarView.date in a TextView. It works fine the first time I press the button but doesn't update the TextView when I select a new date and press the button again.
Here's my MainActivity.kt:
package com.androidas.mindscape
import android.app.Activity
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.Menu
import android.view.MenuItem
import android.view.View
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(findViewById(R.id.tlbMain))
}
override fun onCreateOptionsMenu(menu:Menu):Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.toolmenu, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem) = when (item.itemId) {
R.id.actAdd -> {
textView2.text = "reset" //my idea was to reset the text first, but this didn't work.
textView2.text = (clvMain.date).toString()
true
}
R.id.actPersonalities -> {
// User chose the "Favorite" action, mark the current item
// as a favorite...
true
}
R.id.actList -> {
true
}
else -> {
// If we got here, the user's action was not recognized.
// Invoke the superclass to handle it.
super.onOptionsItemSelected(item)
}
}
}
Where R.id.actAdd is an item in the overflow menu of the action bar.
The way I see it, the TextView should update with the newly selected date as soon as I press the button. Any ideas? Maybe it's simple, but I can't figure it out.
Cheers!
Add this declaration var dateString: String = Date().toString() before onCreate().
Add this code in onCreate():
clvMain.setOnDateChangeListener { view, year, month, dayOfMonth ->
dateString = "$year/$month/$dayOfMonth"
}
and use dateString instead of (clvMain.date).toString()

Categories

Resources