I am modifying the Android TV sample from Android Studio. Originally clicking on a "TV show", it plays the stored URL on the PlaybackVideoFragment in PlaybackActivity. Now I am modifying to let user choose between using the internal mediaPlayer, or calling an intent to show other system video players. After moving the intent call from "OnItemViewClickedListener" to other function call, it gives some kinds of "null reference". I think it is the "context" problem, but trying "this", "activity", "MainActivity.this", the stored "ctx", etc won't work.
MainActivity.kt
class MainActivity : Activity() {
//...
fun prepareVideo(item: Movie){
if(item.videoUrl == ""){
getVideoUrl(item)
}else{
MainFragment().play(item)
}
}
}
MainFragment.kt
class MainFragment : BrowseFragment() {
private inner class ItemViewClickedListener : OnItemViewClickedListener {
override fun onItemClicked(
itemViewHolder: Presenter.ViewHolder,
item: Any,
rowViewHolder: RowPresenter.ViewHolder,
row: Row
) {
if (item is Movie) {
MainActivity().prepareVideo(item)
} else if (item is String) {
Toast.makeText(activity, item, Toast.LENGTH_SHORT).show()
}
}
}
fun play(item: Movie, extra_url: String = ""){
//should be more lines to do some more things. simplified.
val intent = Intent(activity, PlaybackActivity::class.java)
intent.putExtra("item", item)
startActivity(intent)
}
}
May I know how to fix this, so I can switch to "PlaybackActivity"?
Thanks.
Related
STORY:-
I'm making app like Instagram Reels, with viewPager2 with RecyclerViewAdapter, all functionality working fine,
only one problem I'm facing right now is when I click on comment button the Comment screen is opening in another activity and when I getting back on reels activity after commenting or without commenting,
video playing again and mixing with current audio that means notifyItemChanged is not working properly in my case,
I just want to update that comment count only without affecting whole adapter items.
Class- AdapterClass : RecyclerView.Adapter
onClickListener for Comment button
binding.ivComment.setOnClickListener(View.OnClickListener {
if (Utility.isOnline(context)) {
itemClick(post)
} else {
val toast: Toast = Toast.makeText(
context,
R.string.msg_no_internet,
Toast.LENGTH_SHORT
)
toast.show()
}
})
fun itemClick(postList: PostList?) {
if (Utility.isOnline(context)) {
val intent = Intent(context, BitesCommentActivity::class.java)
intent.putExtra(PostList.SENDEXTRA, postList)
context.startActivity(intent)
} else {
val toast: Toast = Toast.makeText(context, R.string.msg_no_internet, Toast.LENGTH_SHORT)
toast.show()
}
}
Class- Activity Class
Using BroadcastReceiver for update comment count from CommentActivity
private val mMessageReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val isActivityInForeground = this#BitesSwappableVideosActivity.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)
if (!isActivityInForeground) {
val message = intent.getStringExtra("message")
val epost_id = intent.extras!!
.getLong(Constant.ScreenExtras.EPOST_ID, -1)
val action = intent.extras!!.getInt(Constant.ScreenExtras.ACTION)
for (i in fav.indices) {
val postList: PostList = fav[i]
val updateIndex: Int
if (action == Constant.COMMENT) {
if (postList.postId == epost_id) {
postList.totalComment = postList.totalComment + 1
eBiteReels_adapter.notifyItemChanged(i)
break
}
}
}
} else {
val message = intent.getStringExtra("message")
val epost_id = intent.extras!!
.getLong(Constant.ScreenExtras.EPOST_ID, -1)
val action = intent.extras!!.getInt(Constant.ScreenExtras.ACTION)
for (i in fav.indices) {
val postList: PostList = fav[i]
if (action == Constant.COMMENT) {
if (postList.postId == epost_id) {
postList.totalComment = postList.totalComment + 1
eBiteReels_adapter.notifyItemChanged(i)
break
}
}
}
}
}
}
I've tried
eBiteReels_adapter.notifyItemChanged(i)
but it cause problem with another items especially with videoplayer.
if I remove notifyItemChanged from my code it refreshes comment count when I restart my SwipableVideoActivity
inside MainActivity I have snapShot listener to document added that calls a function inside a fragment that supose to set and update the adapter of item that stored in fire store
mFireStore.collection(Constans.BOARDS)
.whereArrayContains(Constans.ASSIGNED_TO,FireStore().getCurrentUid())
.orderBy("timeStamp", Query.Direction.DESCENDING)
.addSnapshotListener { value, e ->
Log.d("MainActivity","board listener")
if (e != null) {
Log.w(ContentValues.TAG, "Listen failed.", e)
return#addSnapshotListener
}
val boards = ArrayList<Board>()
Constans.BOARDS_CHATS_LIST = ArrayList()
for (doc in value!!) {
val board = doc.toObject(Board()::class.java)
Constans.BOARDS_CHATS_LIST.add(board)
}
fragment_chat().updateBoardToUi(Constans.BOARDS_CHATS_LIST)
}
and here is the function
fun updateBoardToUi(boardsChatsList: ArrayList<Board>) {
if(boardsChatsList.size > 0){
val context = getContext() ?: return
Log.e("${Constans.BOARDS_CHATS_LIST.size.toString()}","updateBoardToUi")
view?.rv_chats_list?.visibility = View.VISIBLE
view?.no_chats_avlible?.visibility = View.GONE
view?.rv_chats_list?.layoutManager = LinearLayoutManager(context)
view?.rv_chats_list?.setHasFixedSize(true)
//might be an error
adapter = BoardItemsAdapter(context,Constans.BOARDS_CHATS_LIST)
view?.rv_chats_list?.adapter = adapter
adapter.notifyItemInserted(0)
adapter.setOnClickListener(
object :BoardItemsAdapter.OnClickListener{
override fun onClick(position: Int, model: Board) {
Log.i("fragment chat", "on click")
val intent = Intent(context, ChatActivity::class.java)
intent.putExtra(Constans.BOARD_CHAT_DETAILS, model)
intent.putExtra("uid", FirebaseAuth.getInstance().currentUser?.uid )
intent.putExtra(Constans.DOCUMENT_ID, model.documentId)
intent.putExtra("position", position)
startActivity(intent)
}
}
)
}else{
Log.e("inside","updateBoardToUi2")
view?.no_chats_avlible?.visibility = View.VISIBLE
}
}
but the adapter deas not show the new item added even thogh I use adapter.notifyItemInserted(0)
It is because you can not hold and send data with "Constants.BOARDS_CHATS_LIST". Because every time you want to call it, it will return the default value it has. You can do 4 things that come into my mind:
1- Send the data from activity to fragment via Shared Preferences. I do not recommend this method.
2 - Send data from activity to fragment via bundle. This is doable but i do not prefer it.
3 - Move your firestore function to the fragment and declare a global list and put the records there, then use it in updateBoardToUi function. You can do this but if you need this function in other fragment, you need to copy and paste it there too.
4- You can create a new class for firestore functions, and whenever you need it, call it from there. This is the best way and i will try to help you with it.
Create new kotlin class and paste this inside it. You will later call this inside onViewCreated of your fragment, and it will send the array to the updateBoardToUi method.
class FirestoreClass {
private val mFireStore = FirebaseFirestore.getInstance()
private val mFirebaseAuth = FirebaseAuth.getInstance()
fun getBoards(fragment: YourFragmentName) {
mFireStore.collection(Constans.BOARDS)
.whereArrayContains(Constans.ASSIGNED_TO,getCurrentUserID())
.orderBy("timeStamp", Query.Direction.DESCENDING)
.addSnapshotListener { value, e ->
if (e != null) {
Log.w(ContentValues.TAG, "Listen failed.", e)
return#addSnapshotListener
}
val boards = ArrayList<Board>()
for (doc in value!!) {
val board = doc.toObject(Board()::class.java)
boards.add(board)
}
fragment.updateBoardToUi(boards)
}
}
fun getCurrentUserID(): String {
val currentUser = mFirebaseAuth.currentUser
var currentUserID = ""
if (currentUser != null) {
currentUserID = currentUser.uid
}
return currentUserID
}
}
Now we will use the list from your db.
fun updateBoardToUi(boardsChatsList: ArrayList<Board>) {
// fragment.updateBoardToUi(boards) that sent the data and now
// it is in boardsChatsList, you will use this.
if(boardsChatsList.size > 0){
val context = getContext() ?: return
Log.e("${boardsChatsList.size.toString()}","updateBoardToUi")
view?.rv_chats_list?.visibility = View.VISIBLE
view?.no_chats_avlible?.visibility = View.GONE
adapter = BoardItemsAdapter(context,boardsChatsList)
view?.rv_chats_list?.adapter = adapter
view?.rv_chats_list?.layoutManager = LinearLayoutManager(context)
view?.rv_chats_list?.setHasFixedSize(true)
adapter.setOnClickListener(
object :BoardItemsAdapter.OnClickListener{
override fun onClick(position: Int, model: Board) {
Log.i("fragment chat", "on click")
val intent = Intent(context,ChatActivity::class.java)
intent.putExtra(Constans.BOARD_CHAT_DETAILS, model)
intent.putExtra("uid", FirestoreClass().getCurrentUserID())
intent.putExtra(Constans.DOCUMENT_ID, model.documentId)
intent.putExtra("position", position)
startActivity(intent)
}
}
)
}else{
Log.e("inside","updateBoardToUi2")
view?.no_chats_avlible?.visibility = View.VISIBLE
}
}
And finally call that db function in your fragment's onViewCreated to activate all of this. If you do not have onViewCreated just paste this code:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
FirestoreClass().getUsersList(this)
}
All of this can be too much, but this is the best practice. If you learn this convention, you will easily adapt working anywhere.
I waas develop an app in kotlin, when I get to the following error:
rise by the following check in the first line:
(it.responseBase as ValidateOtpResponse).let {resp -> // error rise here
if (resp.code == "200") {
val sucessDialog = GenericDialog(
context = requireContext(),
icon = R.drawable.ic_tick_green,
title = getString(R.string.change_password_title),
subtitle = getString(R.string.password_change_sucess),
buttonText = getString(R.string.understand),
cancelable = true,
clickListener = { (activity as DashboarActivity).redirectToLogin() }
)
sucessDialog.show(requireFragmentManager(), "sucess_otp_dialog")
} else {
showOtpError().also {
(activity as DashboarActivity).redirectToLogin()
}
}
}
and the arquitecture of the clases in the app is this:
data class ValidateOtpResponse(
#SerializedName("code")
val code: String
) : Serializable, ResponseBase()
and their parent:
open class ResponseBase : Serializable
Have this any sense? Because I being using this kind of cast along the app, and it's works until now
So if you can throw some light into this issue, take thanks in advance !
I try to apply the change which suggest Slaw, and Hakshay, and I've done something like this at repository level, which I guess it should works, but it doesn't:
Activity.class
(it.responseBase).let {resp ->
if ((resp as ValidateOtpResponse).code == "200") {
val sucessDialog = GenericDialog(
context = requireContext(),
icon = R.drawable.ic_tick_green,
title = getString(R.string.change_password_title),
subtitle = getString(R.string.password_change_sucess),
buttonText = getString(R.string.understand),
cancelable = true,
clickListener = { (activity as DashboarActivity).redirectToLogin() }
)
sucessDialog.show(requireFragmentManager(), "sucess_otp_dialog")
} else {
showOtpError().also {
(activity as DashboarActivity).redirectToLogin()
}
}
}
Repository.class
override fun onResponse(
call: Call<ValidateOtpResponse>,
response: Response<ValidateOtpResponse>
) {
/**
* We set as response the code, which tells if the call works: 200: OK - 400: KO
*/
if(response.isSuccessful){
response.let {
var value: WrapperResponse<ValidateOtpResponse> = WrapperResponse(response.body() as ValidateOtpResponse, ErrorBase(ErrorBase.ErrorType.NON_ERROR))
(value.responseBase as ValidateOtpResponse).code = response.code().toString()
Log.d("pass", "code after change: ${(value.responseBase as ValidateOtpResponse).code}")
validateChangePasswordOtpLD.postValue(value)
}
}else{
var error : ErrorBase
response.let {
error = ErrorBase(it.errorBody()!!.string(), it.code(), ErrorBase.ErrorType.STRING)
}
validateChangePasswordOtpLD.postValue((WrapperResponse(ResponseBase(), error)))
}
}
override fun onFailure(call: Call<ValidateOtpResponse>, t: Throwable) {
validateChangePasswordOtpLD.postValue(WrapperResponse(ResponseBase(), ErrorBase()))
}
I try to set the response from the API, and then modify the atribute of the response before set to the LD.
Although this changes, I still getting the cast exception when I try to recover the data into the activity.
Your response is of type ResponseBase which is the superclass. You are trying to cast it to type ValidateOtpResponse which is a subclass. You would not be able to cast an object of superclass into an object of the subclass.
For example:
You need to fetch the response of type ValidateOtpResponse.
Writing an Android app with Kotlin using Android Studio. I have several activities and each of them has similar buttons. I added global variable ACTID which references each Activity I have through the map.
Every button has android: onClick="onClick" in its XML file.
So, I tried to make a public function:
public fun allClick(view: View){
val context = ACTIVITY_DICT[ACTID]
val toast = Toast.makeText(context, ACTID.toString(), Toast.LENGTH_LONG)
toast.show()
when (view.id)
{
R.id.nextBtn -> {
val intentNext = Intent(context, ACTIVITY_DICT[ACTID+1]!!::class.java)
context?.startActivity(intentNext)
context?.finish()}
R.id.backBtn -> {
val intentBack = Intent(context, ACTIVITY_DICT[ACTID-1]!!::class.java)
context?.startActivity(intentBack)
context?.finish()}
}
}
However, I cannot set allCLick for onClick. How can I fix it? Would be grateful for any possible help.
You can make a base activity BaseActivity, implement allClick(view: View) method in it and inherit from it other activities:
class BaseActivity : AppCompatActivity() {
public fun allClick(view: View) {
val context = ACTIVITY_DICT[ACTID]
val toast = Toast.makeText(context, ACTID.toString(), Toast.LENGTH_LONG)
toast.show()
when (view.id) {
R.id.nextBtn -> {
val intentNext = Intent(context, ACTIVITY_DICT[ACTID+1]!!::class.java)
context?.startActivity(intentNext)
context?.finish()
}
R.id.backBtn -> {
val intentBack = Intent(context, ACTIVITY_DICT[ACTID-1]!!::class.java)
context?.startActivity(intentBack)
context?.finish()
}
}
}
}
Also add android: onClick="allClick" for every button in its XML file.
I am building a chatting application in android using Kotlin. I want to long press on a message to copy it on the clipboard. This function is working fine but I am unable to show a toast due to some error. Here is my code.
class ChatFromItem(val text: String, val user: User): Item<ViewHolder>() {
override fun bind(viewHolder: ViewHolder, position: Int) {
viewHolder.itemView.textView_from_row.text = text
viewHolder.itemView.textView_from_row.setOnLongClickListener {
Toast.makeText(this, "Message copied", Toast.LENGTH_LONG).show()
val clipboard = it.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val myClip = ClipData.newPlainText("label", text)
clipboard.primaryClip = myClip
true
}
val uri = user.profileImageUrl
val target = viewHolder.itemView.imageView_chat_from_row
Picasso.get().load(uri).into(target)
}
override fun getLayout(): Int {
return R.layout.chat_from_row
}
}
Some people might get confused as of why there is a 'true' written at the very bottom of .setOnLongClickListener. Without the toast statement the rest of the code is working fine. I have tested it many times.
I am including an image containing the error message that android studio is showing me.
Thank you in advance.
When you are using setOnLongClickListener { /* your code here */ } in kotlin it is the equivlent to:
setOnLongClickListener(new OnLongClickListener() {public boolean onLongClick(View v){ /* your code here */ }}); in java
Therefore when you do this:
viewHolder.itemView.textView_from_row.setOnLongClickListener {
Toast.makeText(this, "Message copied", Toast.LENGTH_LONG).show()
....
}
It's the equivalent to this:
setOnLongClickListener(new OnLongClickListener() {
#Override
public boolean onLongClick(View v) {
Toast.makeText(this, "Message copied", Toast.LENGTH_LONG).show()
}
});
As you can see the this in the code above is not of the type Context it's of the type OnLongClickListener!
You can find a context using the view that you have:
Toast.makeText(viewHolder.itemView.context, "Message copied", Toast.LENGTH_LONG).show()
You need to pass context or get it from a view, like itemView.context.