I got a problem when converting my java code to kotlin.
This line of code is what making me the problem
dots = arrayOfNulls<TextView>(layouts.size)
it said:
Type mismatch. Required: Array(TextView)? - Found: Array(TextView?)
Let you here the whole code and hope you can give me hand to figure out what's wrong.
class WelcomeActivity : AppCompatActivity() {
private var viewPager: ViewPager? = null
private var myViewPagerAdapter: MyViewPagerAdapter? = null
private var dotsLayout: LinearLayout? = null
private var dots: Array<TextView>? = null
private var layouts: IntArray? = null
private var btnSkip: Button? = null
private var btnNext: Button? = null
private var prefManager: PrefManager? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Checking for first time launch - before calling setContentView()
prefManager = PrefManager(this)
if (!prefManager!!.isFirstTimeLaunch) {
launchHomeScreen()
finish()
}
// Making notification bar transparent
if (Build.VERSION.SDK_INT >= 21) {
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
}
setContentView(R.layout.welcomescreen)
viewPager = findViewById(R.id.view_pager) as ViewPager
dotsLayout = findViewById(R.id.layoutDots) as LinearLayout
btnSkip = findViewById(R.id.btn_skip) as Button
btnNext = findViewById(R.id.btn_next) as Button
// layouts of all welcome sliders
// add few more layouts if you want
layouts = intArrayOf(R.layout.welcomescreen_slide1, R.layout.welcomescreen_slide2, R.layout.welcomescreen_slide3, R.layout.welcomescreen_slide4)
// adding bottom dots
addBottomDots(0)
// making notification bar transparent
changeStatusBarColor()
myViewPagerAdapter = MyViewPagerAdapter()
viewPager!!.adapter = myViewPagerAdapter
viewPager!!.addOnPageChangeListener(viewPagerPageChangeListener)
btnSkip!!.setOnClickListener { launchHomeScreen() }
btnNext!!.setOnClickListener {
// checking for last page
// if last page home screen will be launched
val current = getItem(+1)
if (current < layouts!!.size) {
// move to next screen
viewPager!!.currentItem = current
} else {
launchHomeScreen()
}
}
}
private fun addBottomDots(currentPage: Int) {
dots = arrayOfNulls<TextView>(layouts.size)
val colorsActive = resources.getIntArray(R.array.array_dot_active)
val colorsInactive = resources.getIntArray(R.array.array_dot_inactive)
dotsLayout!!.removeAllViews()
for (i in dots!!.indices) {
dots[i] = TextView(this)
dots!![i].text = Html.fromHtml("•")
dots!![i].textSize = 35f
dots!![i].setTextColor(colorsInactive[currentPage])
dotsLayout!!.addView(dots!![i])
}
if (dots!!.size > 0)
dots!![currentPage].setTextColor(colorsActive[currentPage])
}
Thanks in advance.
[EDIT]: Also, I noticed an error in a for statement, here:
for (i in dots!!.indices) {
dots[i] = TextView(this)
dots!![i].text = Html.fromHtml("•")
dots!![i].textSize = 35f
dots!![i].setTextColor(colorsInactive[currentPage])
dotsLayout!!.addView(dots!![i])
}
First, in the first line said Unresolve reference indices; then in second line say that dots[i] cannot smart cast to 'Array?' because it's a mutable property and finally in the lines behind every dot before dots!![i] says "Only safe (?) or not-null asserted (!!.) calls are allowed on a nullable receiver of type TextView?
An array of nulls is basically an array with every element equal to null. That means that your variable's type must accept nulls as well. To accomplish this you need to change your variable type to Array<TextView?>?.
Use:
private lateinit var dots: Array<TextView?>
Instead of:
private var dots: Array<TextView>? = null
Related
I'm trying to display a 4x4 grid with values that change depending on user input. To achieve that, I created mutableStateListOf that I use in a ViewModel to survive configuration changes. However, when I try to replace a value in that particular list using button onClick, it keeps doing that until app crashes. I can't understand why is onReplaceGridContent looping after clicking the button once. Currently, my code looks like this:
ViewModel:
class GameViewModel : ViewModel(){
var gameGridContent = mutableStateListOf<Int>()
private set // Restrict writes to this state object to private setter only inside view model
fun replaceGridContent(int: Int, index: Int){
gameGridContent[index] = int
}
fun removeGridContent(index: Int){
gameGridContent[index] = -1
}
fun initialize(){
for(i in 0..15){
gameGridContent.add(-1)
}
val firstEmptyGridTile = GameUtils.getRandomTilePosition(gameGridContent)
val firstGridNumber = GameUtils.getRandomTileNumber()
gameGridContent[firstEmptyGridTile] = firstGridNumber
}
}
Button:
Button(
onClick = {
onReplaceGridContent(GameUtils.getRandomTileNumber(),GameUtils.getRandomTilePosition(gameGridContent))},
colors = Color.DarkGray
){
Text(text = "Add number to tile")
}
Activity Composable:
#Composable
fun gameScreen(gameViewModel: GameViewModel){
gameViewModel.initialize()
MainStage(
gameGridContent = gameViewModel.gameGridContent,
onReplaceGridContent = gameViewModel::replaceGridContent,
onRemoveGridContent = gameViewModel::removeGridContent
)
}
Your initialize will actually run on every recomposition of gameScreen:
You click on a tile - state changes causing recomposition.
initializa is called and changes the state again causing recomposition.
Step 2 happens again and again.
You should initialize your view model in its constructor instead (or use boolean flag to force one tim initialization) to make it inly once.
Simply change it to constructor:
class GameViewModel : ViewModel(){
var gameGridContent = mutableStateListOf<Int>()
private set // Restrict writes to this state object to private setter only inside view model
fun replaceGridContent(int: Int, index: Int){
gameGridContent[index] = int
}
fun removeGridContent(index: Int){
gameGridContent[index] = -1
}
init {
for(i in 0..15){
gameGridContent.add(-1)
}
val firstEmptyGridTile = GameUtils.getRandomTilePosition(gameGridContent)
val firstGridNumber = GameUtils.getRandomTileNumber()
gameGridContent[firstEmptyGridTile] = firstGridNumber
}
}
Now you don't need to call initialize in the composable:
#Composable
fun gameScreen(gameViewModel: GameViewModel){
MainStage(
gameGridContent = gameViewModel.gameGridContent,
onReplaceGridContent = gameViewModel::replaceGridContent,
onRemoveGridContent = gameViewModel::removeGridContent
)
}
I created a scrollView programmaticaly that contains 20 views each with an image and a text.
I have two questions :
1 - is the id assignment correct and is my setOnClickListener correct?
2 - By which method onClick can I know which view of the scrollView the user has clicked?
See my code below
private var integerList: MutableList<Int>? = mutableListOf()
private var cellNo: MutableList<String>? = mutableListOf()
private var sv_mvmtChoosed = ""
private fun showSpinner() {
/* SCROllL VIEW */
var linearLayout: LinearLayout? = null
linearLayout = findViewById(R.id.linear1)
val layoutInflater = LayoutInflater.from(this)
var randIndex = 0
for (posIndex in 0..19) {
val rand = Random()
randIndex = rand.nextInt(20)
while (integerList!!.contains(randIndex)) {
randIndex = rand.nextInt(20)
}
integerList!!.add(randIndex)
// Create the view...
val view: View = layoutInflater.inflate(R.layout.scroll_bckgrnd, linearLayout, false)
// give it an id
view.id = generateViewId()
view.setOnClickListener(this)
cellNo!!.add(view.id.toString())
println(cellNo)
//... then populate it with image and text
val iv = view.findViewById<ImageView>(R.id.iv)
iv.setImageResource(sv_photoImage[randIndex])
val tv = view.findViewById<TextView>(R.id.tv)
tv.text = sv_photoName[randIndex]
linearLayout?.addView(view)
}
// which view the user did select?
fun onClick(view: View?) {
when (view!!.id) {
??? -> doSomething
}
}
}
Any idea to get me back on track will be welcome.
Its probably better to make a new OnClickListener for every view.
view.setOnClickListener(this)
needs to be this
view.setOnClickListener {
// do something
}
or
view.setOnClickListener(createOnClickListner())
fun createOnClickListner():View.OnClickListener{
return object :View.OnClickListener{
override fun onClick(view : View) {
//do something with the view that was clicked
}
}
}
Thanks a lot avalerio.
I finally found a solution as follow :
I replaced :
// give it an id
view.id = generateViewId()
view.setOnClickListener(this)
cellNo!!.add(view.id.toString())
println(cellNo)
with :
// give it an id
view.id = posIndex
view.setOnClickListener(this)
then I did this :
// the onClickListener for my 20 images/text
override fun onClick(view: View?) {
when (view!!.id) {
// Now de position clicked on the ScrollView
in 0..19 -> didHeSucceeded(view!!.id)
}
}
And use the result:
private fun didHeSucceeded(scrllPos: Int) {
// TODO: close de scrollView, how ? :-)
spinnerChoice = nameOfTechScrollVw[scrllPos]
succes = (!allreadyChoosedArray.contains(spinnerChoice)) && (currentArray.contains(spinnerChoice
))
if (succes) {
...
...
}
It works perfectly
In the app I'm working on, it is imperative that all views in all layout files have an id set on them. So I'm trying to build a custom lint rule to enforce this.
Normally, one would use the getApplicableElements() method from XmlScanner and include a list of strings for each element tag. However, I can't seem to find a way to make this look at all elements in an XML layout which subclass View.
I tried using XmlScannerConstants.ALL, however, that looks at every single element in every single XML file. Given that we have several other types of XML-based resources, that's not going to work.
My code for the inspector class is below. Does anyone know of a good way filter getApplicableElements() so it looks at every element that subclasses View, and nothing else?
class IdDetector : ResourceXmlDetector() {
companion object {
private const val ISSUE_ID = "MissingId"
private const val ISSUE_DESCRIPTION = "Missing required attribute 'id'"
private const val ISSUE_EXPLANATION = "Identifiers are required on all views."
val ISSUE = Issue.create(
id = ISSUE_ID,
briefDescription = ISSUE_DESCRIPTION,
explanation = ISSUE_EXPLANATION,
category = Category.A11Y,
priority = 10,
severity = Severity.FATAL,
androidSpecific = true,
implementation = Implementation(IdDetector::class.java, Scope.RESOURCE_FILE_SCOPE)
)
}
override fun getApplicableElements(): Collection<String>? = XmlScannerConstants.ALL
override fun visitElement(context: XmlContext, element: Element) {
if (!element.hasAttributeNS("http://schemas.android.com/apk/res/android", "id")) {
context.report(ISSUE, element, context.getLocation(element), ISSUE_DESCRIPTION)
}
}
}
There's the appliesTo(#NonNull ResourceFolderType folderType) method that can help you achieve that. In your case, I believe you will be only interested in targeting the layout folder. You're new detector would look something like this:
class IdDetector : ResourceXmlDetector() {
companion object {
private const val ISSUE_ID = "MissingId"
private const val ISSUE_DESCRIPTION = "Missing required attribute 'id'"
private const val ISSUE_EXPLANATION = "Identifiers are required on all views."
val ISSUE = Issue.create(
id = ISSUE_ID,
briefDescription = ISSUE_DESCRIPTION,
explanation = ISSUE_EXPLANATION,
category = Category.A11Y,
priority = 10,
severity = Severity.FATAL,
androidSpecific = true,
implementation = Implementation(IdDetector::class.java, Scope.RESOURCE_FILE_SCOPE)
)
}
override fun appliesTo(folderType: ResourceFolderType): Boolean = ResourceFolderType.LAYOUT == folderType
override fun getApplicableElements(): Collection<String>? = XmlScannerConstants.ALL
override fun visitElement(context: XmlContext, element: Element) {
if (!element.hasAttributeNS("http://schemas.android.com/apk/res/android", "id")) {
context.report(ISSUE, element, context.getLocation(element), ISSUE_DESCRIPTION)
}
}
}
I have SecondActitivity and when it starts I assign specific values: listTitle, dbJustForPlaing,currentCursorPosition, numberA and so on.
class SecondActivity : OptionMenuHelper() {
val listTitle: String by lazy { intent.getStringExtra(Values.listTitle) }
lateinit var dbJustForPlaing: DataBaseJustForPlaying
var currentCursorPosition: Int = -1
lateinit var cursorOfWholeList: Cursor
var lastCursorPosition: Int = -1
lateinit var oneRowCursor: Cursor
var numberA = 0
var numberB = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_personal_test)
//some functions
}
}
During the use of this activity, variables are changing. Now I want to add button to SecondActivity, if user presses it, all variables must be returned to same state like they were when activity just started. What is the best way to do that?
I can do this:
fun startNewActitivy(){
val intent = Intent(this, SecondActitivity::class.java)
intent.putExtra("listTitle", listTitle)
startActivity(intent)
finish
}
But I am not sure if this is correct way to do. I need piece of code which will help to easy support my app in future and which also will be efficient in device resource consumption
As pointed out by Peyman in the comments, just create a function that set's the default values for the variables. Something along the following lines,
fun setVariableDefaultValues() {
currentCursorPosition = -1
lastCursorPosition = -1
numberA = 0
numberB = 0
}
You can then call the setVariableDefaultValues() method anywhere you want to reset the values.
After Convert Java File to Kotlin File, gives me two errors in same line: In code below show variables and function with error
lateinit var ncArr: Array<ImageButton>
lateinit var xBitmap: Bitmap
lateinit var oBitmap: Bitmap
lateinit var intArr: IntArray
lateinit var btnStartGame: Button
lateinit var btnMenu: Button
var stop: Boolean = false
var gameMode: Int = 0
var umove: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onStart()
setContentView(R.layout.activity_game)
ncArr = arrayOfNulls(9)
ncArr[0] = findViewById(R.id.nc0) as ImageButton
ncArr[1] = findViewById(R.id.nc1) as ImageButton
ncArr[2] = findViewById(R.id.nc2) as ImageButton
ncArr[3] = findViewById(R.id.nc3) as ImageButton
ncArr[4] = findViewById(R.id.nc4) as ImageButton
ncArr[5] = findViewById(R.id.nc5) as ImageButton
ncArr[6] = findViewById(R.id.nc6) as ImageButton
ncArr[7] = findViewById(R.id.nc7) as ImageButton
ncArr[8] = findViewById(R.id.nc8) as ImageButton
ncArr[0].setOnClickListener(this)
ncArr[1].setOnClickListener(this)
ncArr[2].setOnClickListener(this)
ncArr[3].setOnClickListener(this)
ncArr[4].setOnClickListener(this)
ncArr[5].setOnClickListener(this)
ncArr[6].setOnClickListener(this)
ncArr[7].setOnClickListener(this)
ncArr[8].setOnClickListener(this)
xBitmap = BitmapFactory.decodeResource(resources, R.drawable.x)
oBitmap = BitmapFactory.decodeResource(resources, R.drawable.o)
intArr = IntArray(9)
for (i in 0..8) {
intArr[i] = 0
}
btnMenu = findViewById(R.id.btnMenu) as Button
btnStartGame = findViewById(R.id.btnStartGame) as Button
btnMenu.setOnClickListener(this)
btnStartGame.setOnClickListener(this)
stop = false
gameMode = intent.getIntExtra("game_mode", 1)
umove = 1
}
in line ncArr = arrayOfNulls(9)
There are errors:
I trying to fix this problem but i don't have more ideas...
Any sollution?
thanks in advance
ncArr = arrayOfNulls(9)
The type of ncArr will be Array<ImageButton?>
ncArr[0] = findViewById(R.id.nc0) as ImageButton
You are using unsafe cast operator as, because the cast operator throws an exception if the cast is not possible. Thus, we call it unsafe. What happens if
findViewById(R.id.nc0) as ImageButton
return null, then null cannot assign to ImageButton, it will throw an exception and make your app crash.
To avoid the above error you can use
ncArr[0] = findViewById(R.id.nc0) as ImageButton?
or using safe cast operator as?
ncArr[0] = findViewById(R.id.nc0) as? ImageButton
Remember apply for ncArr[0] to ncArr[8].
Update: Based on your request, you can use this solution:
lateinit var ncArr: Array<ImageButton>
lateinit var xBitmap: Bitmap
lateinit var oBitmap: Bitmap
lateinit var intArr: IntArray
lateinit var btnStartGame: Button
lateinit var btnMenu: Button
var stop: Boolean = false
var gameMode: Int = 0
var umove: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onStart()
setContentView(R.layout.activity_game)
ncArr = arrayOf(
findViewById<ImageButton>(R.id.nc0).apply{setOnClickListener(this#GameActivity)},
findViewById<ImageButton>(R.id.nc1).apply{setOnClickListener(this#GameActivity)},
findViewById<ImageButton>(R.id.nc2).apply{setOnClickListener(this#GameActivity)},
findViewById<ImageButton>(R.id.nc3).apply{setOnClickListener(this#GameActivity)},
findViewById<ImageButton>(R.id.nc4).apply{setOnClickListener(this#GameActivity)},
findViewById<ImageButton>(R.id.nc5).apply{setOnClickListener(this#GameActivity)},
findViewById<ImageButton>(R.id.nc6).apply{setOnClickListener(this#GameActivity)},
findViewById<ImageButton>(R.id.nc7).apply{setOnClickListener(this#GameActivity)},
findViewById<ImageButton>(R.id.nc8).apply{setOnClickListener(this#GameActivity)}
)
xBitmap = BitmapFactory.decodeResource(resources, R.drawable.x)
oBitmap = BitmapFactory.decodeResource(resources, R.drawable.o)
// Your code here
...
}
You trying to put Nullable array into NonNull. You may declare intArr as Array<ImageButton?> and everything will work.