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

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)

Related

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(...)

Button is not reacting as expected, I am trying to see if the function is not working or if there some other error

The function should use user input to do math and output into a text box for the user. On clicking the button, absolutely nothing happens. LogCat isn't showing me anything, so I'm not sure how to Troubleshoot this issue. I've got two similiar activities in the same project that are working fine, so I suspect I may not being doing the math correctly but can't find any other information. Any advice is appreciated.
package com.example.awcc
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main3.*
class Setup : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
editTextNumber120.text.toString().toInt()
}
}
class ThirdActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main3)
val button2 = findViewById< Button >(R.id.button2)
button2.setOnClickListener {
val intent = Intent(this, SecondActivity::class.java)
startActivity(intent)
}
val button3 = findViewById< Button >(R.id.button3)
button3.setOnClickListener {
val intent2 = Intent(this, MainActivity::class.java)
startActivity(intent2)
val button7 = findViewById< Button >(R.id.button35)
button7.setOnClickListener {
var value1 = editTextNumber120.text.toString().toInt()
fun accessory(): Int {
return when {
value1 > 10 -> value1 * 0
value1 in 10..20 -> value1 * 1
value1 in 21..40 -> value1 * 2
value1 in 40..50 -> value1 * 3
value1 in 51..75 -> value1 * 4
value1 < 75 -> value1 * 5
else -> value1
}
}
val complete = accessory().toString()
try {
editTextNumber19?.setText(complete)
} catch (e: NumberFormatException) {
Toast.makeText(
applicationContext,
"Please enter a 0 in the blank field",
Toast.LENGTH_LONG
).show()
}
}
}
}
}
where is editTextNumber120 defined? what is this text being used for?
If you're expecting the value to carry over from the setup activity to activity3, that's not how it works. Each activity uses it's own data and if you need data to be shared across Activities and Fragments, then you need to create a data model for that information.
Example:
public class MyDataModel {
protected MutableLiveData<Int> editTextData;
public MyDataModel() {
editTextData = new MutableLiveData<>();
}
// setter/getter - returns a LiveData object that allows
// you to observe the value for any changes
// If you don't need to observe changes, then just keep it as an int/string
}
This way your other Activity can access the same data being used in the Setup activity. Also note, this doesn't persist across reboots, so if you want the setup to contain the previous data used in the last boot of the app, i'd look into SharedPreferences.
Also I don't know the structure of your app, but I would question why you need so many Activities? My app I'm working on is fairly robust but even I only have one activity (with a couple fragments), and another activity for the settings, and that's it.
I found the answer if anyone runs into this issue behind me. I was using Var instead of Val so it wasn't actually multiplying or changing the var at all. Changed to Val and it works like a charm!

Kotlin- Function runs after next lines ended

Updated- The code in the question works now
I'm trying to run a function after clicking on a button. The function updates an array and I want to run the next lines (The next lines transfer me to another activity) if the array isn't empty.
I tried to open the new activity within the filterPlaces function but with no success, startActivity and Intent don't work.
This is the function that updates the array:
var places = ArrayList<Place>() //Global place array
class MainActivity : AppCompatActivity() {
fun filterPlaces(types: ArrayList<String>, foods: ArrayList<String>, maxPrice: Int, maxProximity: Int) {
var typesList = types
val foodList = foods
if (types.isEmpty()) {
typesList = arrayListOf("Restaurant", "Hangouts")
if (foods.isEmpty()) {
foodList.add("Pizza")
}
}
val db = FirebaseFirestore.getInstance()
db.collection("places").get().addOnSuccessListener { result ->
for (document in result) {
val typeMatches = document.data["Type"].toString() in typesList
val foodMatches = document.data["Food"].toString() in foodList
var price = 0
when (document.data["Price"].toString()) {
"Very cheap" -> price = 0
"Cheap" -> price = 1
"Average" -> price = 2
"Far" -> price = 3
"Very far" -> price = 4
}
val priceMatches = price <= maxPrice
var proximity = 0
when (document.data["Proximity"].toString()) {
"Very close" -> proximity = 0
"Close" -> proximity = 1
"Far" -> proximity = 2
"Very far" -> proximity = 3
}
val proximityMatches = proximity <= maxProximity
if (typeMatches and foodMatches and priceMatches and proximityMatches) {
val place = Place(
document.data["Place"].toString(),
document.data["Type"].toString(),
document.data["Food"].toString(),
document.data["Price"].toString(),
document.data["Proximity"].toString()
)
places.add(place)
Log.d("name", "Place added successfully")
}
}
//Openning the results activity
if (places.isNotEmpty()) {
val i = Intent(this, RelevantPlaces::class.java)
val b = Bundle()
b.putParcelableArrayList("places", places)
i.putExtra("bundle", b)
startActivity(i)
}
}
.addOnFailureListener { exception ->
Log.d("name", "Error getting documents.")
}
}
This is the on click function:
fun onSortFilterClicked(view: View) {
if (places.isEmpty()) filterPlaces(types, foods, priceRange.progress, proximityRange.progress)
}
I want to run filterPlaces first, update the places array while I run it, and only than check if the array is still empty and if not open the new activity.
What actually happens is that it calls the filterPlaces but doesn't do it, instead it checks the places array (The if condition in the code) and only than goes into filterPlaces and run what's in it, resulted in me need to press twice on the button and only than the array has values.
I'm running this on Android Studio and I'm new at this Kotlin world and android developing in general.
Is there a solution for this? Either open the activity within the function or make the function run first?
What is happening?
An asynchronous request is made in filterPlaces, this is why the method itself returns immediately and passes control to the next code block.
How to fix this?
Move your code starting another Activity into the scope of your success listener. A better approach is to place this code into a separate method and just call it as needed.
Placed the filterPlace function outside the Main Activity class. Moved the function in the Main Activity class and it worked.

Required lifecycle owner found Activity

I am using android library androidx.appcompat:appcompat:1.0.2. Working on a sample of codelabs work manager. I need to get live data from ViewModel and I am using this
mViewModel!!.getOutputWorkInfo()?.observe(this, Observer<List<WorkInfo>> {
})
but this variable shows error -
Type mismatch. Required:Lifecycle Owner. Found:BlurActivity
I googled all says no need to extend lifecycle owner, by default appcompact activity implements lifecycle owner.
And I also worked this in another project, no issues found. I don't know why I am getting this error in this project.
`
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.RadioGroup
import com.bumptech.glide.Glide
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.work.WorkInfo
class BlurActivity : AppCompatActivity() {
private var mViewModel: BlurViewModel? = null
private var mImageView: ImageView? = null
private var mProgressBar: ProgressBar? = null
private var mGoButton: Button? = null
private var mOutputButton: Button? = null
private var mCancelButton: Button? = null
/**
* Get the blur level from the radio button as an integer
* #return Integer representing the amount of times to blur the image
*/
private val blurLevel: Int
get() {
val radioGroup = findViewById<RadioGroup>(R.id.radio_blur_group)
return when (radioGroup.checkedRadioButtonId) {
R.id.radio_blur_lv_1 -> 1
R.id.radio_blur_lv_2 -> 2
R.id.radio_blur_lv_3 -> 3
else -> 1
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_blur)
// Get the ViewModel
mViewModel = ViewModelProviders.of(this).get(BlurViewModel::class.java)
// Get all of the Views
mImageView = findViewById(R.id.image_view)
mProgressBar = findViewById(R.id.progress_bar)
mGoButton = findViewById(R.id.go_button)
mOutputButton = findViewById(R.id.see_file_button)
mCancelButton = findViewById(R.id.cancel_button)
// Image uri should be stored in the ViewModel; put it there then display
val intent = intent
val imageUriExtra = intent.getStringExtra(Constants.KEY_IMAGE_URI)
mViewModel!!.setImageUri(imageUriExtra)
if (mViewModel!!.imageUri != null) {
Glide.with(this).load(mViewModel!!.imageUri).into(mImageView!!)
}
mViewModel!!.getOutputWorkInfo()?.observe(this, Observer<List<WorkInfo>> {
// If there are no matching work info, do nothing
if (it == null || it.isEmpty()) return#Observer
// We only care about the first output status.
// Every continuation has only one worker tagged TAG_OUTPUT
val workInfo = it[0]
val finished = workInfo.state.isFinished
if (!finished) showWorkInProgress() else showWorkFinished()
})
// Setup blur image file button
mGoButton!!.setOnClickListener { view -> mViewModel!!.applyBlur(blurLevel) }
}
/**
* Shows and hides views for when the Activity is processing an image
*/
private fun showWorkInProgress() {
mProgressBar!!.visibility = View.VISIBLE
mCancelButton!!.visibility = View.VISIBLE
mGoButton!!.visibility = View.GONE
mOutputButton!!.visibility = View.GONE
}
/**
* Shows and hides views for when the Activity is done processing an image
*/
private fun showWorkFinished() {
mProgressBar!!.visibility = View.GONE
mCancelButton!!.visibility = View.GONE
mGoButton!!.visibility = View.VISIBLE
}
}
`
Same problem here, so I had to update my androidx.appcompat dependency, like below:
implementation 'androidx.appcompat:appcompat:1.1.0-alpha04'
no need to implement LifecycleOwner (as its implemented by default now {as mentioned by Darthcow})
After implementing LifeCycleOwner in Main Activity, error goes and work properly
Updated
Use latest androidx lib and u don't need to implement LifecycleOwner. Now it is implemented by default in ComponentActivity which AppcompatActivity implements
This issue occurs when there is mismatch between appcompat and Lifecycle dependency.
Either use this set of dependencies:
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version
implementation 'androidx.appcompat:appcompat:1.1.0-alpha04'
Or:
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation "android.arch.lifecycle:extensions:$lifecycle_version"
I got this error while trying to parse a context as a Life cycle owner and a helpful way to solve it is using a type cast
context as LifecycleOwner

How to get the size of installed application?

I am trying to calculate the size of the installed application.
I found an answer here
I have tested it on some devices, and there is no problem except Samsung Galaxy Note3(4.3).
I get this error: java.lang.NoSuchMethodException: getPackageSizeInfo()
I'm wondering is there any other way to get the size of an installed app?
try this...
public static long getApkSize(Context context, String packageName)
throws NameNotFoundException {
return new File(context.getPackageManager().getApplicationInfo(
packageName, 0).publicSourceDir).length();
}
Maybe this link will help you:
http://nsamteladze.blogspot.com/2012/10/get-apks-code-size-in-android.html
Basically he is making use of the concept of reflection, which is a powerful tool but not always advisable. Read more about it here :What is reflection and why is it useful? and if it is suitable for you, make use of it.
You can get Size of apps without AIDL Files -------> Kotlin Language
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val intent = Intent(Intent.ACTION_MAIN)
intent.addCategory(Intent.CATEGORY_LAUNCHER)
val list = packageManager.queryIntentActivities(intent,0)
// Set adapter to LIST VIEW
listView.adapter = getApps(list)
}
private fun getApps(List: MutableList<ResolveInfo>): List<AppData> {
val list = ArrayList<AppData>()
for (packageInfo in List) {
val packageName = packageInfo.activityInfo.packageName
// return size in form of Bytes(Long)
val size = File(packageManager.getApplicationInfo(packageName,0).publicSourceDir).length()
val item = AppData(Size)
list += item
}
return list
}
}
// Make Data Class
data class AppData(val size: Long)
Remember to convert it in MB from Bytes

Categories

Resources