Hello iam learning build apps with kotlin but i got stack with this error says "Required Iterable, Found List", how i can solve this problem? please see my code below thanks
class MainActivity : AppCompatActivity(),ProductView {
private lateinit var productAdapter: ProductAdapter
private var productList: MutableList<ProductData> = mutableListOf()
private lateinit var dataPresenter : DataPresenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initRecycler();
getProduct()
}
private fun getProduct() {
dataPresenter = DataPresenter(applicationContext,this)
dataPresenter.getProduct()
}
private fun initRecycler() {
productAdapter = ProductAdapter(this,productList)
rvMain.layoutManager = LinearLayoutManager(this,LinearLayoutManager.VERTICAL,false)
rvMain.adapter = productAdapter
}
override fun showLoading() {
pgMain.visibility = View.VISIBLE
}
override fun hideLoading() {
pgMain.visibility = View.GONE
}
override fun showProduct(products: List<ProductData>?) {
if (products?.size != 0){
this.productList.clear()
this.productList.addAll(products) // <= Required Iterable<ProductData>, Found List<ProductData>
productAdapter.notifyDataSetChanged()
}
}
}
I suspect that the error message actually is:
Required Iterable<ProductData>, Found List<ProductData>?
The question mark at the end is not just punctuation. That is the nullable indicator in Kotlin. A List<ProductData> cannot be null, but a List<ProductData>? can. And I believe that addAll() requires a non-null value.
Ideally, you should change ProductView so that the signature for showProduct() is fun showProduct(products: List<ProductData>).
Alternatively, you could rewrite showProduct() to be:
override fun showProduct(products: List<ProductData>?) {
if (products?.size != 0){
this.productList.clear()
products?.let { this.productList.addAll(it) }
productAdapter.notifyDataSetChanged()
}
}
Related
hi im currently using kotlin for my android project,as per instruction,i was told to make an apps that has recycleview to show list item and intent when you click on one of the list shown.
but i have this error when i want to run the app,the error was "No value passed for parameter 'ListUserAdapter'"
here is my code
ListUserAdapter.kt
class ListUserAdapter(private val ListUserAdapter: ArrayList<User>) : RecyclerView.Adapter<ListUserAdapter.ListViewHolder>() {
private lateinit var onItemClickCallBack: OnItemClickCallBack
fun setOnItemClickCallback(onItemClickCallback: OnItemClickCallBack) {
this.onItemClickCallBack = onItemClickCallback
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ListViewHolder {
val view: View = LayoutInflater.from(parent.context).inflate(R.layout.row_user, parent, false)
return ListViewHolder(view)
}
override fun onBindViewHolder(holder: ListViewHolder, position: Int) {
val (name, username) = ListUserAdapter[position]
holder.tvName.text = name
holder.tvUserName.text= username
holder.itemView.setOnClickListener {
onItemClickCallBack.onItemClicked(ListUserAdapter[holder.adapterPosition])
}
}
override fun getItemCount(): Int = ListUserAdapter.size
class ListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var tvName: TextView = itemView.findViewById(R.id.tv_username)
var tvUserName: TextView = itemView.findViewById(R.id.tv_name)
}
interface OnItemClickCallBack {
fun onItemClicked(data : User)
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var adapter: ListUserAdapter
private lateinit var dataName: Array<String>
private lateinit var dataUsername: Array<String>
private lateinit var dataLocation: Array<String>
private lateinit var dataRepo: Array<String>
private lateinit var dataCompany: Array<String>
private lateinit var dataFollowers: Array<String>
private lateinit var dataFollowing: Array<String>
private lateinit var dataPhoto: TypedArray
private var users = arrayListOf<User>()
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setAdapter()
prepare()
addItem()
}
private fun setAdapter() {
adapter = ListUserAdapter() //error in here
with(binding) {
rvList.adapter = adapter
rvList.layoutManager =
GridLayoutManager(this#MainActivity, 2, GridLayoutManager.HORIZONTAL, false)
rvList.setHasFixedSize(true)
}
adapter.setOnItemClickCallback(object : ListUserAdapter.OnItemClickCallBack{
override fun onItemClicked(user: User) {
val intent = Intent(this#MainActivity, DetailActivity::class.java)
intent.putExtra(DetailActivity.KEY_USER, user)
startActivity(intent)
}
})
}
private fun prepare() {
dataName = resources.getStringArray(R.array.name)
dataUsername = resources.getStringArray(R.array.username)
dataPhoto = resources.obtainTypedArray(R.array.avatar)
dataLocation = resources.getStringArray(R.array.location)
dataRepo = resources.getStringArray(R.array.repository)
dataCompany = resources.getStringArray(R.array.company)
dataFollowers = resources.getStringArray(R.array.followers)
dataFollowing = resources.getStringArray(R.array.following)
}
private fun addItem() {
for (position in dataName.indices) {
val user = User(
dataUsername[position],
dataName[position],
dataLocation[position],
dataCompany[position],
dataRepo[position],
dataFollowers[position],
dataFollowing[position],
dataPhoto.getResourceId(position, -1)
)
users.add(user)
}
}
}
DetailActivity.kt
class DetailActivity : AppCompatActivity() {
private lateinit var binding: ActivityDetailBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityDetailBinding.inflate(layoutInflater)
setContentView(binding.root)
setData()
}
private fun setData() {
val dataUser = intent.getParcelableExtra<User>(KEY_USER) as User
with(binding) {
Glide.with(root)
.load(dataUser.photo)
.circleCrop()
.into(ivDetailAvatar)
}
}
companion object {
const val KEY_USER = "key_user"
}
}
just you need to replace adapter = ListUserAdapter() //error in here with adapter = ListUserAdapter(users) then your problem solve
You're just forgetting the constructor parameter :)
Your adapter class needs to receive a ArrayList of User to be instantiated, you already have it in your Activity, you just need to pass it as the constructor parameter :)
I would also rename the parameter name to something like users or usersList instead of `ListUserAdapter but because currently it is misleading, since this parameter is not an adapter, it is a list of users.
Just change the line with error to
adapter = ListUserAdapter(users)
But I think it is best for you to first call prepare() and addItem() and then instantiate your adapter. Or you can instantiate your adapter, but also create a addItems function, since it is a best practice for adapters.
fun addItems(users: List<User>) {
this.ListUserAdapter.addAll(users)
notifyDataSetChanged()
}
and use it after setting everything up inside onCreate doing something like
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setAdapter()
prepare()
addItem()
adapter.addItems(users)
}
but you could probably reorganize these methods to improve readability as well, but it will work :)
I'm trying to integrate a VerticalGridSupportFragment() inside a BrowseSupportFragment() based on some of the Leanback examples.
On running the code below (partial snippet provided below) I get the exception java.lang.IllegalArgumentException: Fragment must implement MainFragmentAdapterProvider
I'm struggling to understand how to implement the interface methods defined by MainFragmentAdapterProvider [i.e getMainFragmentAdapter()]. Could anyone provide insight surrounding this issue, or could you provide an example? I've tried to return the mRowsAdapter, however it was not of the correct type.
Any help would really be appreciated, thanks!
class MainFragmentByGroupFragment : BrowseSupportFragment() {
override fun onActivityCreated(savedInstanceState: Bundle?) {
Log.i(TAG, "onCreate")
super.onActivityCreated(savedInstanceState)
prepareBackgroundManager()
mainFragmentRegistry.registerFragment(
PageRow::class.java,
PageRowFragmentFactory(mBackgroundManager)
)
setupUIElements()
createRows()
setupEventListeners()
}
private class PageRowFragmentFactory(backgroundManager: BackgroundManager) : BrowseSupportFragment.FragmentFactory<androidx.fragment.app.Fragment>() {
override fun createFragment(rowObj: Any?): androidx.fragment.app.Fragment {
val row = rowObj as Row
return ChannelFragment()
}
}
}
class ChannelFragment : VerticalGridSupportFragment(), OnItemViewClickedListener, BrowseSupportFragment.MainFragmentAdapterProvider {
private var mRowsAdapter: ArrayObjectAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mRowsAdapter = ArrayObjectAdapter(CardPresenter())
setAdapter(mRowsAdapter)
setOnItemViewClickedListener(this)
createRows()
}
private fun createRows() {
val gridPresenter = VerticalGridPresenter()
gridPresenter.numberOfColumns = NUM_COLUMNS
setGridPresenter(gridPresenter)
setOnItemViewClickedListener(this)
val list = MovieList.list
for (i in 0 until 100) {
mRowsAdapter!!.add(list[i % 5])
}
}
override fun onItemClicked(
itemViewHolder: Presenter.ViewHolder?, item: Any,
rowViewHolder: RowPresenter.ViewHolder?, row: Row?
) {
}
override fun getMainFragmentAdapter(): BrowseSupportFragment.MainFragmentAdapter<*> {
TODO("Not yet implemented")
}
companion object {
private const val NUM_COLUMNS = 5
}
}
I think you should implement getMainFragmentAdapter.
Add the following to ChannelFragment:
private val mMainFragmentAdapter: BrowseSupportFragment.MainFragmentAdapter<*> =
object : BrowseSupportFragment.MainFragmentAdapter<Fragment?>(this) {
override fun setEntranceTransitionState(state: Boolean) {
}
}
And return mMainFragmentAdapter in getMainFragmentAdapter:
override fun getMainFragmentAdapter(): BrowseSupportFragment.MainFragmentAdapter<*> {
return mMainFragmentAdapter
}
I am building a video player app for Android TV. I am using Exoplayer leanback dependency as explained in https://developer.android.com/training/tv/playback/transport-controls.
So far I've been able to display the video title, which is static, but I need to display a subtitle that is dynamic, it changes whenever the video playing changes. How can I do it?
The image below shows how the video player looks like. I've used a subtitle phrase as a placeholder on where it should appear.
I was able to solve the problem. I added a listener in the VideoSupportFragment class, Player.EventListener.
This way:
class VideoFragment(mediaItems: Map<String, Any>) : VideoSupportFragment(), Player.EventListener {
private var playerAdapter: ExoPlayerAdapter? = null
private var _mediaItems: Map<String, Any>? = null
private lateinit var mMediaPlayerGlue: VideoMediaPlayerGlue<ExoPlayerAdapter>
private var mItems: List<*>? = null
init {
_mediaItems = mediaItems
}
private val mHost: VideoSupportFragmentGlueHost = VideoSupportFragmentGlueHost(this)
private fun playWhenReady(glue: PlaybackGlue) {
if (glue.isPrepared) {
glue.play()
} else {
glue.addPlayerCallback(object : PlaybackGlue.PlayerCallback() {
override fun onPreparedStateChanged(glue: PlaybackGlue) {
if (glue.isPrepared) {
glue.removePlayerCallback(this);
glue.play()
}
}
})
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val mediaSource = _mediaItems?.get("media_source") as Map<*, *>
playerAdapter = ExoPlayerAdapter(activity!!, _mediaItems!!)
mMediaPlayerGlue =
VideoMediaPlayerGlue(activity, playerAdapter!!)
mMediaPlayerGlue.host = mHost;
mMediaPlayerGlue.isControlsOverlayAutoHideEnabled = true
mItems = mediaSource["media_items"] as List<*>
mMediaPlayerGlue.title = mediaSource["title"] as CharSequence?
mMediaPlayerGlue.playerAdapter.setDataSource()
mMediaPlayerGlue.isSeekEnabled = true
playerAdapter?.player?.addListener(this);
playWhenReady(mMediaPlayerGlue)
}
override fun onPause() {
super.onPause()
playerAdapter?.player?.pause()
}
override fun onResume() {
super.onResume()
playerAdapter?.player?.play()
}
override fun onDestroy() {
super.onDestroy()
playerAdapter?.player?.removeListener(this);
}
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
if (mItems?.size!! > 1){
val item : Map<*, *> = mItems!![playerAdapter?.player?.currentWindowIndex!!] as Map<*, *>
mMediaPlayerGlue.subtitle = item["subtitle"] as String
}
}
}
I am uning MVVM pattern when i call api in activity from ViewModel its always throwing me with Error
Smart cast to 'MainActivityViewModel' is impossible, because 'binding.mainModel' is a complex expression
Following is the my ViewMode:
class MainActivityViewModel(private val api: SearchAPI) : BaseViewModel() {
private var query: String = ""
get() = if (field.isEmpty()) "MVVM" else field
private val _refreshing: NotNullMutableLiveData<Boolean> = NotNullMutableLiveData(false)
val refreshing: NotNullMutableLiveData<Boolean>
get() = _refreshing
lateinit var _items: NotNullMutableLiveData<RetrofitWrapper>
val items: NotNullMutableLiveData<RetrofitWrapper>
get() = _items
fun getMainPageData() {
val params = mutableMapOf<String, String>().apply {
this["version"] = "v1"
this["locale"] = "en"
this["platform"] = "android"
}
addToDisposable(api.getHomePageDetail(params).with()
.doOnSubscribe { _refreshing.value = true }
.doOnSuccess { _refreshing.value = false }
.doOnError { _refreshing.value = false }
.subscribe({
_items.value = it
}, {
// handle errors
})
)
}
}
And following is my MainActivity:
class MainActivity : BindingActivity<ActivityMain2Binding>() {
override fun getLayoutResId(): Int {
return R.layout.activity_main2
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding.mainModel = getViewModel()
binding.setLifecycleOwner(this)
binding.mainModel.getMainPageData()
}
}
Your Help on this matter highly appreciated, do let me know why this is happening to me.
binding.mainModel is a mutable, nullable variable.
Generated binding setter method code will look something like this :
public void setViewModel(#Nullable MainActivityViewModel viewModel) { ... }
When you call binding.mainModel.getMainPageData() it cannot infer that the variable is not null.
You can either do :
binding.mainModel!!.getMainPageData()
or more safely :
binding.mainModel?.getMainPageData()
I am teaching myself Kotlin and android dev. So, i'm sure most of my issue is lack of knowledge, but i've been hung up on this part for day or two. I think my issue is partly my JSON query, and mostly my rookieness.
In my for loop below, I'm getting the following error from the IDE "For-Loop range must have an 'iterator()' method". This is in regards to 'cycloneList' in: for(stormInfo in cycloneList)
I've linked my "dummy" JSON data I'm using can be found here: https://api.myjson.com/bins/19uurt to save a bit of space here in the question.
Problem code
`var cycloneList = response?.body()?.currenthurricane?.stormInfo?.get(0)
if (cycloneList != null) {
for (stormInfo in cycloneList) { <<--Problem
val newCyclone = "Name: ${cycloneList.stormName}"
cycloneStrings.add(newCyclone)
}
}`
FULL CODE
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//creates a new CycloneRetriever object from CycloneHelp.kt
var retriever = CycloneRetriever()
val callback = object : Callback<Cyclones> {
override fun onResponse(call: Call<Cyclones>?, response: Response<Cyclones>?) {
println("Got a response!")
var cycloneList = response?.body()?.currenthurricane?.stormInfo?.get(0)
var cycloneStrings = mutableListOf<String>()
if (cycloneList != null) {
for (stormInfo in cycloneList) { //TODO Figure this out!!
val newCyclone = "Name: ${cycloneList.stormName}"
cycloneStrings.add(newCyclone)
}
}
var layoutMovieListView = movieListView
var movieListAdapter = ArrayAdapter(this#MainActivity, android.R.layout.simple_list_item_1,
cycloneStrings)
layoutMovieListView.adapter = movieListAdapter
}
override fun onFailure(call: Call<Cyclones>?, t: Throwable?) {
println("The thing, it failed!")
}
}
retriever.getCyclones(callback)
}
I'm using Retrofit to access/handle JSON data
Retrofit JSON code
interface WeatherWunderGroundAPI {
#GET("bins/19uurt")
fun getCyclones() : Call<Cyclones>
}
class Cyclones(val currenthurricane: CurrentHurricane)
class CurrentHurricane(val stormInfo: List<StormInfo>)
class StormInfo(val stormName: String)
class CycloneRetriever {
val service : WeatherWunderGroundAPI
init {
val = retrofitCyclone
Retrofit.Builder().baseUrl("https://api.myjson.com/")
.addConverterFactory(GsonConverterFactory.create()).build()
service = retrofitCyclone.create(WeatherWunderGroundAPI::class.java)
}
fun getCyclones(callback: Callback<Cyclones>) {
val call = service.getCyclones()
call.enqueue(callback)
}
}
Your stormInfo is a List which provides the mandatory iterator() and this is what you want to iterate over:
var cycloneList = response?.body()?.currenthurricane?.stormInfo ?: emptyList()
for (stormInfo in cycloneList) { }
Hope it helps...