Kotlin AddOnPageChangeListener not working - android-studio

I wanted to connect a viewPager with an adapter (like this: viewPager.addOnPageChangeListener()), but the letters of the pageChangeListener just turn red like it wasn't a valid code...What am I doing wrong?? Heres a screenshot:
[1]: https://i.stack.imgur.com/IOYJY.png
Context: I'm currently working on a game with a few fragments where you can choose your game cards. I need the pageChangeListener to change the pictures of them cards. Maybe there could be another way to do this but i don't know how...
package com.suffv1
import android.os.Bundle
import androidx.appcompat.app.ActionBar
import androidx.appcompat.app.AppCompatActivity
import androidx.viewpager.widget.ViewPager
import com.example.suff_02.Adapter2
import com.example.suff_02.R
import com.example.suff_02.kartenmodell
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity(){
private lateinit var actionbar: ActionBar
private lateinit var liste: ArrayList<kartenmodell>
private lateinit var myAdapter: Adapter2
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
super.setContentView(R.layout.activity_main)
actionbar = this.supportActionBar!!
loadCards()
viewpager2.addOnPageChangeListener(object: ViewPager.OnPageChangeListener{
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
val title = liste[position].KartenImage
actionbar.title = title
}
})
}
private fun loadCards() {
liste = ArrayList()
liste.add(kartenmodell(R.drawable.bier_radler_klein_level_1))
liste.add(kartenmodell(R.drawable.bier_hopfentrunk_klein_level_1))
liste.add(kartenmodell(R.drawable.bier_butt_light_klein_level_1))
liste.add(kartenmodell(R.drawable.bier_becks_klein_level_1))
liste.add(kartenmodell(R.drawable.bier_tyskie_klein_level_1))
myAdapter = Adapter2(this, liste)
viewpager2.adapter = myAdapter
viewpager2.setPadding(100, 0, 100, 0)
}
}

Looks like you are using ViewPager2 not the original Viewpager and Viewpager2 does not have Page Change Listeners it instead has Page Change Callbacks
So you are using the wrong method to get notified when a pages is changed.
Instead do something like
var myPageChangeCallback = object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
Toast.makeText(this#MainActivity, "Selected position: ${position}",
Toast.LENGTH_SHORT).show()
}
}
viewpager.registerOnPageChangeCallback(myPageChangeCallback)
Though architecturally a lot of times it is bad form to use a OnPageChangeCallback as you are likely breaking the encapsulation idea of the Fragment and it can be better to use the lifecycle state change to Resumed of the Fragment to do things when a page(Fragment) is selected. e.g. put the code in the Fragments onResume method.
Though in this case of setting the actionbar title it is probably ok architecturally to use a OnPageChangeCallback

To create a AddOnPageChangeListener(), you do it as:
//See how, you have to use object to create an Object of the interface OnPageChangeListener.
//This is how you do for other listeners/interfaces/class as well when you've to implement its member functions instead of new in java.
viewPager.addOnPageChangeListener(object: ViewPager.OnPageChangeListener{
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
TODO("Not yet implemented")
}
override fun onPageSelected(position: Int) {
TODO("Not yet implemented")
}
override fun onPageScrollStateChanged(state: Int) {
TODO("Not yet implemented")
}
})
But, further in Kotlin, you can use Kotlin functions as
//This is for onPageSelected only.
viewPager.onPageChangeListener{ position: Int ->
//This position is the selected Page's position
}

Related

Jetpack Compose: force recomposition when a state changes from another activity

I have two Activities: ActivityOne.kt and ActivityTwo.kt. Both of them use Jetpack Compose to display an UI. In the first one, an item (let's say, a Text) has to be shown with variable showtText is true, and hidden when it's false. I achieve this by using:
#Composable
fun MyUI(){
AnimatedVisibility(visible = viewModel.showText) {
Text("Some text")
}
}
My variable showText is defined in a ViewModel such as:
val showText by mutableStateOf(false)
That way, anytime I change the value of showText within my ViewModel when ActivityOne.kt is visible, it appears or disaappears. What I want to achieve is the following:
From ActivityOne.kt the user can navigate to ActivityTwo.kt (with the first one running in background, it's not cleared from the stack).
There, there's a Switch that can toggle showText value.
When the user press "back", ActivityTwo.kt calls finish() and it dissapears, showing again ActivityOne.kt (it was in the stack).
If user has toggled showText value in ActivityTwo.kt, the text should be hidden or shown automatically, as the state of showText has changed.
The problem is that, although the value of showText does change, the UI in ActivityOne.kt does not respond to those changes. I've checked that recomposition of ActivityOne.kt's UI is not taking place after ActivityTwo.kt is finished, because it is not remembering the previous state of showText.
How could I achieve that? Thanks in advance!
EDIT 1
The problem is a little more difficult as I explained, but the basis are the same. Here is my complete code:
ActivityOne.kt:
class ActivityOne : ComponentActivity() {
private lateinit var vm: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val vmFactory = MainViewModelFactory()
vm = ViewModelProvider(this, vmFactory).get(MainViewModel::class.java)
setContent {
MyTheme {
Surface {
MyUI()
}
}
}
}
#Composable
fun MyUI() {
AnimatedVisibility(visible = myPrefs.showText) {
Text("Some text")
}
}
}
MainViewModel.kt:
class MainViewModel : ViewModel() {
companion object {
val myPrefs by mutableStateOf( AppPrefs() )
}
}
ActivityTwo.kt:
class ActivityTwo : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyTheme {
Surface {
MyUI2()
}
}
}
}
#Composable
fun MyUI2() {
val showText = remember {
mutableStateOf(MainViewModel.myPrefs.showText)
}
Switch(
checked = showText.value,
onCheckedChange = {
showText.value = it
MainViewModel.myPrefs.showText = it
}
)
}
}
AppPrefs.kt:
class AppPrefs {
var showText: Boolean = false
}
The recomposition is not taking place because mutableStateOf is tracking the state of myPrefs which is never changed (the reference itself never changes).
You can achieve the recomposition in a few ways.
Here I will be assuming that your AppPrefs class either already contains more than one member/field, or that you would want to add more members to it in the future easily. That is why I added 2 more properties to it in the samples below and I only suggested solutions where adding more members won't affect the existing code.
Option 1
If you can change the AppPrefs class to track the state of each property individually, then make these changes
class MainViewModel : ViewModel() {
companion object {
val myPrefs = AppPrefs()
}
}
class AppPrefs {
var showText by mutableStateOf(false)
var prop2 by mutableStateOf(0)
var prop3: String? by mutableStateOf(null)
}
Everything else stays the same. Here the recomposition works again because now the state is tracked on members that are actually being changed.
Option 2
If you can't (or don't want to) have mutableStateOf inside AppPrefs you can change AppPrefs from a normal class to a data class. In that way you get the copy function automatically implemented (alongside equals, hashCode, toString and componentN for destructuring support).
class MainViewModel : ViewModel() {
companion object {
// the only change here is val -> var
var myPrefs by mutableStateOf(AppPrefs())
}
}
// data class instead of a normal class
data class AppPrefs(
val showText: Boolean = false,
val prop2: Int = 0,
val prop3: String? = null,
)
In this case you have to also change how you change the value of myPrefs. This is what makes the recomposition work correctly again
onCheckedChange = {
showText.value = it
MainViewModel.myPrefs = MainViewModel.myPrefs.copy(showText = it)
}
In case you cannot use a data class, then you can still go with option 2, but you implement the copy function on your existing class
// normal class with a copy function
class AppPrefs(
val showText: Boolean = false,
val prop2: Int = 0,
val prop3: String? = null,
) {
fun copy(
showText: Boolean = this.showText,
prop2: Int = this.prop2,
prop3: String? = this.prop3,
) = AppPrefs(showText, prop2, prop3)
}
As a bonus, if you go with any of the above solutions, you can even simplify your existing code for the MyUI2 composable and the recomposition will still work.
Example:
#Composable
fun MyUI2() {
val showText = MainViewModel.myPrefs.showText
Switch(
checked = showText,
onCheckedChange = {
// for Option 1
MainViewModel.myPrefs.showText = it
// for Option 2
MainViewModel.myPrefs = MainViewModel.myPrefs.copy(showText = it)
}
)
}

Android Studio Kotlin app "keeps stopping" when I run it

I am just trying to set up a spinner configured as a dropdown menu, but I can't even test my app to see if it works because every time I run it, it immediately crashes. I know that my issue is related to a null object reference at line 21 in my MainActivity.kt file. Here is the problem code:
val spinner: Spinner = findViewById<Spinner>(R.id.locations)
The id of the spinner is locations, so I'm not sure why this is coming back as a null value.
Here is also the full code for the file:
import android.app.Activity
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.Spinner
import android.widget.ArrayAdapter
import android.widget.AdapterView
private var userLocation: Any = ""
private var userDestination: Any = ""
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
val spinner: Spinner = findViewById<Spinner>(R.id.locations)
val locationsAdapter: ArrayAdapter<CharSequence> = ArrayAdapter.createFromResource(
this,
R.array.rooms,
android.R.layout.simple_spinner_item
).also { adapter ->
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinner.adapter = adapter
}
}
class SpinnerActivity : Activity(), AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View?, pos: Int, id:Long) {
userLocation = parent.getItemAtPosition(pos)
val spinner: Spinner = findViewById(R.id.locations)
spinner.onItemSelectedListener = this
}
override fun onNothingSelected(parent: AdapterView<*>) = Unit
}
Declare spinner global for MainActivity:
private lateinit var spinner: Spinner
On the method onCreate initialise the spinner and set its adapter, I would recommend you to set the array adapter as follows:
spinner = findViewById<Spinner>(R.id.locations)
spinner.adapter = ArrayAdapter(this, R.layout.drop_down_generic_item, resources.getStringArray(R.array.rooms))
I don't really understand the SpinnerActivity you showed in the question, sorry. But in MainActivity you can set the spinner.onItemSelectedLister() as follows:
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(adapterView: AdapterView<*>?, view: View?, position: Int, p3: Long) {
val selectedRoom = adapterView?.getItemAtPosition(position)
}
override fun onNothingSelected(p0: AdapterView<*>?) {
}
}
I don't want to confuse you, but have a look at a TextInputLayout with an AutoCompleteTextView, just as a hint. :D

How to add onClickListener on images in recyclerView?

I am trying to attach onclick listener on images in this code, using Glide framework but I'm not able to find a solution for that. Can someone help with this?
Images are showing in emulator already.
Thanks in advance
package com.example.andoridlifecycle.adapters
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.example.andoridlifecycle.R
import kotlinx.android.synthetic.main.item_custom_row.view.*
class ItemAdapter(private val context: Context, private val urls: ArrayList<String>) :
RecyclerView.Adapter<ItemAdapter.ViewHolder>() {
/**
* Inflates the custom view which is designed in xml layout file
*/
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(
LayoutInflater.from(context).inflate(
R.layout.item_custom_row,
parent,
false
)
)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
// Array with urls
val url = urls[position]
Glide.with(context)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.placeholder(R.drawable.placeholder)
.into(holder.imageView)
}
// Gets the number of items in the list
override fun getItemCount(): Int {
return urls.size
}
// A ViewHolder describes an item view and metadata about its place within the RecyclerView.
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val imageView: ImageView = view.iv_image
}
}
I'm Java Developer so I cannot code with Kotlin but I'll show you how to set OnClickListener on your imageView. For using OnclickListener on imageView no need for Glide Library.
Just add the below lines in your onBindViewHolder
holder.imageView.setOnClickListener(view -> {
Toast.makeText(context, "this is number: "+position+" Image Selected", Toast.LENGTH_SHORT).show();
// or your code for imageView
});
Your new code looks like the below:-
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
// Array with urls
val url = urls[position]
//code for onClick on image
holder.imageView.setOnClickListener(view -> {
Toast.makeText(context, "this is number: "+position+" Image Selected", Toast.LENGTH_SHORT).show();
// or your code for imageView
});
Glide.with(context)
.load(url)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.placeholder(R.drawable.placeholder)
.into(holder.imageView)
}
Make sure to change JAVA to Kotlin in my code. If you any problem is causing, Let me know

How can I preview a Compose view when it need a parameter in Android Studio?

I can run Code A in Android Studio, I hope to preview UI when I'm designing, so I added Code B to Code A.
But Code B can't work, why? How can I fix it?
Code A
class MainActivity : ComponentActivity() {
private val handleMeter by viewModels<HandleMeter>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
SoundMeterTheme {
Surface(color = MaterialTheme.colors.background) {
Greeting(handleMeter)
}
}
}
}
}
#Composable
fun Greeting(handleMeter: HandleMeter) {
...
}
Code B
#Preview(showBackground = true)
#Composable
fun DefaultPreview() {
SoundMeterTheme {
val handleMeter by viewModels<HandleMeter>()
Greeting(handleMeter)
}
}
Unfortunately, you can't.
Preview does not support creating ViewModels and NavHost yet, for our bad.
But what you can do instead is to use a fake data or a static data into ui, until the design finish and when you are ready to actually run it, replace the static data with the view model data.
You can use dagger and hilt to inject the view model constructor and then call up the hilt view model in the preview e.g.
#HiltViewModel
class DataFieldsViewModel #Inject constructor(
) : ViewModel() {
Then in your preview code for your composable
#Preview(showBackground = true)
#Composable
fun PreviewDataFieldsScreen() {
DataFieldsScreen(
navController = rememberNavController(),
viewModel = hiltViewModel()
)
}

Struggling to transition code from AppcompatActivity to Fragment. Unable to use setSupportActionBar(toolbar)

I'm trying to transition from using AppcomatActivity to Fragment, because I'm updating my app but I've run into a problem. I get a "Unresolved reference: supportFragmentManager" and "Unresolved reference: setSupportActionBar"(That's all the Logcat shows me) when I try to run my app. Now I chose to switch to using fragments, because I also want to change the apps UI, usage is quicker than the previous version. Anyway here's my code:
Recorder Fragment
package com.khumomashapa.notes.fragments
import android.os.Build
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toolbar
import androidx.annotation.RequiresApi
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter
import androidx.viewpager.widget.ViewPager
import com.astuetz.PagerSlidingTabStrip
import com.khumomashapa.notes.R
import kotlinx.android.synthetic.main.toolbar3.*
class RecorderFragment : Fragment() {
private var tabs: PagerSlidingTabStrip? = null
private var pager: ViewPager? = null
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
activity?.title = "Recorder";
pager = pager?.findViewById<View>(R.id.pager) as ViewPager
pager!!.adapter = MyAdapter(supportFragmentManager)
tabs = tabs?.findViewById<View>(R.id.tabs) as PagerSlidingTabStrip
tabs!!.setViewPager(pager)
val toolbar = toolbar?.findViewById<View>(R.id.toolbar) as Toolbar
toolbar.popupTheme = R.style.ThemeOverlay_AppCompat_Light
setSupportActionBar(toolbar)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_recorder, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
inner class MyAdapter(fm: FragmentManager?) : FragmentPagerAdapter(
fm!!
) {
private val titles = arrayOf(
getString(R.string.tab_title_record),
getString(R.string.tab_title_saved_recordings)
)
override fun getItem(position: Int): Fragment {
when (position) {
0 -> {
return RecordFragment.newInstance(position)
}
1 -> {
return FileViewerFragment.newInstance(position)
}
}
return null!!
}
override fun getCount(): Int {
return titles.size
}
override fun getPageTitle(position: Int): CharSequence? {
return titles[position]
}
}
companion object {
private val LOG_TAG = RecorderFragment::class.java.simpleName
}
}
The purpose of this class is to show a view pager that can switch between to other fragments I've already created. I was able to fix the other errors relating to this like the "Unresolved reference for findViewById" and the "MyAdapter class".
For a Fragment, there are actually two relevant fragment managers. Deciding which one to use depends on your use case.
The Child Fragment Manager
A fragment has a child fragment manager which is responsible for managing its child/nested fragments. You can obtain this with:
Fragment.getChildFragmentManager()
The Parent's Fragment Manager
A fragment also holds reference to it's parent's fragment manager. If the fragment is a direct child of an activity then his represents the activities fragment manager. Otherwise if the fragment is a child of another fragment, it represents the child fragment manager of the parent fragment. This can be obtained with:
Fragment.getParentFragmentManager()
Note that although Fragment has the method Fragment.getFragmentManager(), this is deprecated in favour of Fragment.getParentFragmentManager() so it shouldn't be used.
You can also technically get the activities fragment manager regardless by obtaining a reference to the fragment's activity with Fragment.getAcitivity() and then calling Activity.getSupportFragmentManager(). But generally the parent fragment manager is more useful and clear.

Resources