Dice roller app, formatting attribute id calls using Kotlin - android-studio

I'm taking a kotlin basics course from the android devs website and we have to create an app that allows you to roll a dice and display an image with the number that rolled.
I can make a condition for each possible roll number, but i feel like that's dumb and with formatted reference id calls it should be done way better but I don't know how to accomplish that in kotlin using Android Studio. Any helpers?
package com.example.diceroller
import android.os.Bundle
import android.widget.Button
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
/**
* Calls the onCreate() method to initiate the app in its MainActivity.
* setOnClickListener to look for activity in the window (button presses/taps).
*/
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val rollButton: Button = findViewById(R.id.button)
rollButton.setOnClickListener { rollDice() }
/* Toast.makeText(this,"Dice Rolled!", Toast.LENGTH_SHORT).show() | Shows alert (toast) at bottom of the screen. */
}
/*
* Initiates a 'Dice' instance with 6 sides.
* calls the roll() method to call rng between 1 and numSides.
*
*/
private fun rollDice() {
val dice = Dice(6)
val diceRoll = dice.roll()
val diceImage: ImageView = findViewById(R.id.imageView)
if (diceRoll == 1){ diceImage.setImageResource(R.drawable.dice_1) }
else if (diceRoll == 2){ diceImage.setImageResource(R.drawable.dice_2) }
//Instead of doing it the "dumb way" I want to call the id depending on diceRoll value.
}
}
class Dice(private val numSides: Int) {
fun roll(): Int {
return (1..numSides).random()
}
}

If you're asking how to add 1 or whatever to "R.drawable.dice_" and then turn that string into a resource ID, then you can follow the accepted answer here: https://stackoverflow.com/a/2414165/13598222
Personally I wouldn't recommend that - it's brittle since the compiler can't connect what you're doing to a specific resource. So it can't warn you if you change the resource name (so your code doesn't match anymore), and things like Proguard can end up stripping out "unused" resources (because it can't tell you're using them) and you have to manually fix that.
Probably the most standard way to do what you're doing is to use a when clause, and just return the drawable ID from that:
val drawableId = when(diceRoll) {
1 -> R.drawable.dice_1
2 -> R.drawable.dice_2
3 -> R.drawable.dice_3
4 -> R.drawable.dice_4
5 -> R.drawable.dice_5
else -> R.drawable.dice_6
}
diceImage.setImageResource(drawableId)
Sure it's a little repetitive, but it's a standard pattern, it's very easy to understand and work with, and you're explicitly handling all the cases by adding the (required) else clause - you could also handle 6 explicitly and make the else throw an out-of-range message, in case your roll function somehow sends an invalid value (which it could, since you're hardcoding six values here, but your Dice class can have an arbitrary number!)
Another way you could do it is to have a lookup, like a Map:
private val diceDrawables = mapOf {
1 to R.drawable.dice_1,
2 to R.drawable.dice_2,
3 to R.drawable.dice_3,
4 to R.drawable.dice_4,
5 to R.drawable.dice_5,
6 to R.drawable.dice_6
}
and then your image setting code can be nice and short:
diceImage.setImageResource(diceDrawables[diceRoll] ?: throw Exception("No image for roll: $diceRoll))
This isn't the "dumb" way to do things, it's neat and tidy and avoids repetition (e.g. repeating the setImageResource call). Getting clever with your code logic can cut down a few lines, but it can also make things much more complex and fragile, and just harder to work with. Sometimes that kind of thing is the right call, but in this situation I think this is probably the best compromise. Your call though!

Create a list using those resource ids.
private fun rollDice() {
val diceImages = listOf(R.drawable.dice_1, R.drawable.dice_2, ...)
val diceImage: ImageView = findViewById(R.id.imageView)
discImage.setImageResource(discImages.random())
}

Related

Unable to display the age of an object that was passed to a kotlin function

I'm just starting to learn kotlin and ran into a problem:
I have a Person class that has two fields
-age (Int data type)
-name (data type String)
there is also a oldUp function where I pass a Person object and increment the object's age field by 10.
Before the end of the program ** I want to display the age of the object that was passed to oldUp **
However, age is not shown.
my code:
class Person(var name: String, var age: Int){
}
fun growOld(human: Person){
human.age+=10
}
fun main(args: Array<String>) {
var human = Person("Kitty",6)
growOld(human)
println(human)
}
If you want to print the age, you can just write: println(human.age).
In your example it might be cleaner to add the growOld method to your class so you can call it on the object. For example:
class Person(var name: String, var age: Int){
fun growOld() {
this.age += 10
}
}
fun main() {
var human = Person("Kitty", 6)
println(human.age) // prints 6
human.growOld()
println(human.age) // prints 16
println(human.name) // prints Kitty
}
The problem is you're trying to print the human object itself. Under the hood, this calls its toString() method - every class has one of these, because it's defined on the type all classes derive from. If you don't override it and provide a nice way to "pretty print" your object, it'll use the default implementation, which is basically a reference to the object in memory.
A lot of classes you use have a nice toString() implementation, e.g. if you print a List you get ["something", "that", "looks", "like", "this"]. But that behaviour needed to be coded in - and you need to do that for your Person class too!
So you can override the default implementation like this:
override fun toString(): String {
// return a String here
}
override means you're taking an existing function and writing your own version of it to use instead - if this doesn't match an existing function you can override, you'll get an error. You'll also get an error if you don't use the override keyword for a function that looks exactly like an existing one in a supertype - it's just to make sure you don't accidentally do the wrong thing. In IntelliJ you can do Ctrl+O to override existing functions if you like.
So you could do something like this:
// inside your Person class
override fun toString(): String {
return "Name: $name, age: $age"
}
and then when you use it in a print statement, or in a string (like "Details: $person" or val details = "Details: " + person) it will call that toString() method and get the string you produced.
Another way to approach this is to use a data class:
data class Person(var name: String, var age: Int)
A data class is a special kind of class where all your "data" goes in the constructor (as properties, either val or var), and then you get some boilerplate stuff for free which uses those properties (and only those properties). Things like an equals() and hashCode() implementation that uses that data - and the relevant thing here, it gives you a toString() implementation that pretty prints name and age. Try it out!
Data classes can be really handy for simple data objects like you have here - but in normal classes, overriding toString() yourself is the general way of doing things. And you can still override a data class's toString if you want - sometimes you might want a more complex representation, or nice formatting, or you might want to only include some properties and ignore others. You're in control of how it prints itself!
And if you just want to print the age property, or print anything at all using the data in your object, then you just need to do what Robin's answer says. You don't need a toString() implementation at all for that (and since this is how you usually use objects, often you won't need to write a toString for your own classes at all)

File reading in Kotlin without piggybacking off a button

I am coding a data class that is wanting to read a csv file to grab some information that is stored on the file. How ever, every way that I have tried to read the file will not work.
Here is what I have tried so far:
data class Bird(val birdNumIn: Int){
private var birdNum = birdNumIn
/**
* A function that searches the bird-data.csv file which is a list of birds to find the bird that was
* inputted into the class constructor and then add the values of the bird to the private variables.
*/
fun updateValues(){
var birdNumber = birdNum
var birdInfo: MutableList<String> = mutableListOf()
val minput = InputStreamReader(assets().open("bird-data.csv"), "UTF-8")
val reader = BufferedReader(minput)
}
How ever the assets().open() does not work. It returns an error of trying to open a file that does not exist, but the is in the assets folder, and the filename is spelt right.
I have tried many other methods on trying to read files, like using Java.io.File and using the path of the file.
If you would like to look at our whole project, please feel free to go to our github
What's the assets() function you're calling? This is just a bare data class, it has no connection to the Android environment it's running in, so unless you've injected an AssetManager instance (or a Context to pull it from) into your class, you can't access it.
You probably need to do this:
fun updateValues(context: Context){
val inputStream = context.assets.open("bird-data.csv")
val minput = InputStreamReader(inputStream, "UTF-8")
...
}
which requires your caller to have access to a Context.
Honestly from a quick look at your class, you might want to rework this. Instead of having a bunch of empty fields in your data class (which aren't part of the "data" by the way, only stuff in the constructor parameters is), and then having those updated later by the data class doing some IO, you might want to keep them as just basic stores of data, and create them when you read from your assets file.
So something like:
// all fixed values, initialised during construction
// Also you won't need to override toString now (unless you want to)
data class Bird(
val birdNum: Int
val nameOfBird: String
val birdFilePic: String
val birdFileSong: String
val alternativeName: String
val birdInfoFile: String
) { ... }
Then somewhere else
fun getBirbs(context: Context) {
// open CSV
// read the lines
val allBirds = lines.map {
// parse data for each bird, use it to construct a Bird object
}
}
or whatever you need to do, e.g. loading certain birds by ID.
That way your Bird class is just data and some functions/properties that work with it, it doesn't need a Context because it's not doing any I/O. Something else (that does have access to a Context) is responsible for loading your data and turning it into objects - deserialising it basically. And as soon as it's created, it's ready and initialised and immutable - you don't have to call update on it to get it actually initialised.
And if you ever wanted to do that a different way (e.g. loading a file from the internet) the data class wouldn't need to change, just the thing that does the loading. You could even have different loading classes! One that loads local data, one that fetches from the internet. The point is the separation of concerns, so it's possible to do this kind of thing because that functionality isn't baked into a class that's really about something else.
Up to you but just a thought! Especially if passing the context in like I suggested is a problem - that's a sign your design might need tweaking

Day displayed in Kotlin instead of variable

I am new to kotlin and android studio and i am trying to have the day displayed in a text view. My problme is that only the number 1 to 7 is displayed acording to the current day but not the name of the day what do i have to change to fix this?
val day = Calendar.getInstance().get(Calendar.DAY_OF_WEEK)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
fun dayOfWeek() {
val day = Calendar.getInstance().get(Calendar.DAY_OF_WEEK)
println(
when (day) {
1 -> "Sunday"
2 -> "Monday"
3 -> "Tuesday"
4 -> "Wednesday"
5 -> "Thursday"
6 -> "Friday"
7 -> "Saturday"
else -> "Time has stopped"
}
)
}
tag = findViewById(R.id.Tag)
tag.text = day.toString()
java.time
The java.util Date-Time API and their formatting API, SimpleDateFormat are outdated and error-prone. It is recommended to stop using them completely and switch to the modern Date-Time API*.
Solution using java.time, the modern Date-Time API: There are a couple of ways to do it as shown below:
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.TextStyle;
import java.util.Locale;
public class Main {
public static void main(String[] args) {
// If you want to deal with the system timezone, you can simply use
// LocalDate.now() instead of LocalDate.now(ZoneId.systemDefault()). In order to
// deal with a specific timezone, replace ZoneId.systemDefault() with the
// applicable ZoneId e.g. ZoneId.of("Asia/Kabul")
LocalDate date = LocalDate.now(ZoneId.systemDefault());
String dow = date.getDayOfWeek().getDisplayName(TextStyle.FULL, Locale.ENGLISH);
System.out.println(dow);
// Alternatively,
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("EEEE", Locale.ENGLISH);
dow = dtf.format(date);
System.out.println(dow);
}
}
ONLINE DEMO
Learn more about the modern Date-Time API from Trail: Date Time.
* If you are working for an Android project and your Android API level is still not compliant with Java-8, check Java 8+ APIs available through desugaring. Note that Android 8.0 Oreo already provides support for java.time.
In your code, day is an Int property.
You have created a function called dayOfWeek() that you never call so that code never runs.
When you do tag.text = day.toString(), it is looking at the day property, not the day variable inside your function. It is usually a bad idea to name a variable the same thing as some property in the same class because it makes the code confusing to read. But that doesn't matter either way in this case because both of them are Ints.
In your dayOfWeek() function, you are using a when expression to covert day to a String, but you are just printing it. You are not storing it in a variable such that it can be used for anything else, like setting it on a TextView.
You can make your function return a String, and use it that way. I would also move your function outside the onCreate() function. It is unusual to create a function inside another function for this kind of task.
val day = Calendar.getInstance().get(Calendar.DAY_OF_WEEK)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tag = findViewById(R.id.Tag)
tag.text = getDayOfWeekText()
}
fun getDayOfWeekText() {
return when (day) {
1 -> "Sunday"
2 -> "Monday"
3 -> "Tuesday"
4 -> "Wednesday"
5 -> "Thursday"
6 -> "Friday"
7 -> "Saturday"
else -> "Time has stopped"
}
}
Or to do this the easy way, and with proper language support:
tag.setText(DateTimeFormatter.ofPattern("EEEE").format(LocalDate.now()))

Running methods on different threads

simply put for a hw problem I need to run a bubble sort with 3 million elements. Do this normally. and then sort 4 individual lists (not nearly accurate but is all thats required) of 750000 elements each on a different thread.
I have a class that extends thread that simply prints a line if im running the thread. But I'm not sure how to get it to run a method
class thread extends Thread{
override def run()
{
// Displaying the thread that is running
println("Thread " + Thread.currentThread().getName() +" is running.")
}
}
//this for loop is in an object
for (c <- 1 to 5)
{
var th = new thread()
th.setName(c.toString())
th.start()
}
I am going to stick to answering the question you asked instead of trying to do your homework for you. I hope that is enough to get your started trying things out for yourself.
Recall that class names should be capitalized. That is probably a good thing to remember as your instructor will probably mark you down if you forget that during an exam.
class MyThread extends Thread{
override def run() = {
// Displaying the thread that is running
println("Thread " + Thread.currentThread().getName() +" is running.")
sort()
}
def sort() = {
// my sorting code here
}
}
If your instructor has not restricted you to using Thread only, I would also, similar to Luis Miguel Mejía Suárez, recommend Future instead of Thread. The syntax it uses is cleaner and preferable for how I would do multithreading in a professional setting. However, the choice may not be yours, so if your teacher says use Thread, use Thread.
For a primer on using Futures, see here.
Good luck!

Use groovy categories to add dynamic properties

Expanding on this blog post, I am trying to use a category to create a simple DSL for use with the javax.measure (JSR-275) classes (similar to TimeCategory for time intervals)
However, I do not want to add boilerplate code for each of the possible available methods (getMeter, getMilliMeter, getKelvin, getSecond etc.). I thought overriding the getProperty(String) method would work, but alas, it looks like the getProperty method defined in the category is not used when accessing the property directly.
Here is some simplified code to demonstrate:
import javax.measure.quantity.Length;
import javax.measure.unit.Unit;
import javax.measure.Measure;
#Category(Number)
class LengthCategory {
public Measure<BigDecimal, Length> getProperty(String unit){
return Measure.valueOf(this,Unit.valueOf(unit));
}
}
use(LengthCategory){
println 3.getProperty("m") // this works
println 3.m // this reports a non-exisiting property
prinlln 3.'m' // as does this
}
Assuming other methods of dynamically adding properties to a runtime object (e.g. Expando, subclassing GroovyInterceptible, mixins and other metaclass manipulations) is not viable and I would really rather not have to manually code getters for every possible unit and SI prefix combination. There are obviously other ways to go about creating a DSL for measurements, but I would still like to understand why this method would not work.
Could someone explain why the getProperty method of the category does not override .propertyName usage? I am obviously missing something important about the resolution of property names using the metaclass during runtime.
I don't know why getProperty doesn't work on categories. But you can define a get method on them that does basically the same (i think). This works:
#Category(Number)
class LengthCategory {
def get(String unit) {
"$this $unit"
}
}
use (LengthCategory) {
println 3.m // 3 m
println 3.'m' // 3 m
}
As far as I can tell, you can't actually extend Integers with full (i.e., readable and writable) properties using Category -- only with methods.
You can extend an Integer using read-only properties by using the method version of the property. You can even make it writable by including a set method. However, there doesn't seem to be a way to store the value passed in other than in a static variable and that ends up affecting all Integers.
Example:
$ cat catprop
#!/usr/local/bin/groovy
#Category(Integer)
class CatInteger {
private static String str = "default"
public static String setN(Integer i, String _str) { str = _str }
public static String getN(Integer i) { return str }
}
use (CatInteger) {
3.n = "333a"
println "3.n is " + 3.n
3.n = "333b"
println "3.n is " + 3.n
4.n = "444"
println "4.n is " + 4.n
println "3.n is " + 3.n
}
$ catprop
3.n is 333a
3.n is 333b
4.n is 444
3.n is 444
$
Note that in the last line 3.n return "444" because the stored field is static. I suppose that one could use a private HashMap and store a value for every Integer accessed, but that's too ugly to contemplate.
Another possibility would be to use the MetaClass Interface's getProperty() and setProperty(). However, I haven't looked into that so I don't know if it would work or not (just a thought).
Nice answer, but not sure, if you's still want to use JSR-275 now that JSR-363 is final?;-)

Resources