how save state of Activity for save Chats in kotlin - android

hi i am new in android and kotlin and i have a chat app that will work with sms and want save state of all chat for next run
i did use recyclerView and GroupieViewHolder for show chats , i see some post here but all was with java but i am using kotlin for this app
so if you can please help me and If possible, state the easiest way in the simplest possible way
app screenshot:
my smsActivity.kt:
class smsActivity : AppCompatActivity() {
private val messageAdapter = GroupAdapter<GroupieViewHolder>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sms)
val recyclerView = findViewById<RecyclerView>(R.id.listmessage)
val button = findViewById<Button>(R.id.button)
val editmessage = findViewById<EditText>(R.id.editText)
val sharedPeref = getSharedPreferences("setNumber", MODE_PRIVATE)
val number = sharedPeref.getString("number", null)
recyclerView.adapter = messageAdapter
populateData()
receiveAutoResponse()
button.setOnClickListener {
val message = Message(text = editmessage.text.toString(), sendBy = "me")
val smssend = editmessage.text.toString()
val sendMessageItem = SendMessageItem(message)
messageAdapter.add(sendMessageItem)
editmessage.text.clear()
receiveAutoResponse()
recyclerView.scrollToPosition(messageAdapter.getItemCount() - 1);
try {
val smsManager: SmsManager = SmsManager.getDefault()
smsManager.sendTextMessage(number, null, smssend, null, null)
Toast.makeText(
applicationContext,
"بخاری 3 =دریافت گزارش ✅ دریافت گزارش چندین ثانیه زمان می برد ، لطفا برای ارسال دستور بعدی کمی صبر کنید",
Toast.LENGTH_LONG
).show()
} catch (e: Exception) {
Toast.makeText(
applicationContext,
"لطفا ابتدا شماره سیستم را وارد کنید !",
Toast.LENGTH_SHORT
).show()
val intent = Intent(this, setnumberActivity::class.java)
this.startActivity(intent)
}
}
}
fun ScrollView.scrollToBottom() {
val lastChild = getChildAt(childCount - 1)
val bottom = lastChild.bottom + paddingBottom
val delta = bottom - (scrollY + height)
smoothScrollBy(0, delta)
}
private fun populateData() {
val data = listOf<Message>()
data.forEach {
if (it.sendBy == "me") {
messageAdapter.add(SendMessageItem(it))
} else {
messageAdapter.add(ReceiveMessageItem(it))
}
}
}
private fun receiveAutoResponse() {
GlobalScope.launch(Dispatchers.Main) {
delay(1000)
val sharedPeref = getSharedPreferences("setNumber", MODE_PRIVATE)
val nums = "+98" + sharedPeref.getString("number", null)
val cursor: Cursor? = getContentResolver().query(
Uri.parse("content://sms"),
null,
"address='$nums'",
null,
null
)
cursor?.moveToFirst()
val messageSend = cursor?.getString(12)
val receive = Message(text = "$messageSend", sendBy = "me")
val receiveItem = ReceiveMessageItem(receive)
messageAdapter.add(receiveItem)
val recyclerView = findViewById<RecyclerView>(R.id.listmessage)
recyclerView.scrollToPosition(messageAdapter.getItemCount() - 1);
}
}
}
class SendMessageItem(private val message: Message) : BindableItem<ItemMessageSendBinding>() {
override fun getLayout(): Int {
return R.layout.item_message_send
}
override fun bind(viewBinding: ItemMessageSendBinding, position: Int) {
viewBinding.message = message
}
}
class ReceiveMessageItem(private val message: Message) : BindableItem<ItemMessageReceiveBinding>() {
override fun getLayout(): Int {
return R.layout.item_message_receive
}
override fun bind(viewBinding: ItemMessageReceiveBinding, position: Int) {
viewBinding.message = message
}
}

If you want ensure that Activity re-creation doesn't destroy your content, you can refer to this answer
If you want to connect the data to your local Storage, I suggest you using Room

Related

Why is the .update("field","value") not working?

If you see the read messages function in my activity class below, i wanted to update the isSeen field in firestore, but for some reason it does not work at all. My guess it requires a specific document value but that would not be possible as this a messaging app so there will be a lot of documents created.
Activity Class
class MessageActivity : AppCompatActivity() {
private lateinit var binding: ActivityMessageBinding
private lateinit var chat: ArrayList<Message>
private lateinit var messageAdapter: MessageAdapter
private lateinit var roomID: String
private lateinit var userID: String
private lateinit var recID: String
private var c: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMessageBinding.inflate(layoutInflater)
setContentView(binding.root)
userID = FirebaseAuth.getInstance().uid.toString()
recID = intent.getStringExtra("userID").toString()
val recName:String = intent.getStringExtra("userName").toString()
binding.userName.text = recName
chat = arrayListOf()
messageAdapter = MessageAdapter(chat)
binding.recyclerView.setHasFixedSize(true)
binding.recyclerView.layoutManager = LinearLayoutManager(this)
when {
userID < recID ->
{
roomID = userID + recID
}
userID.compareTo(recID) == 0 ->
{
Toast.makeText(this, "Error you are chatting with yourself!!!", Toast.LENGTH_SHORT).show()
}
else -> {
roomID = recID + userID
}
}
readMessages(userID,recID)
binding.btnSend.setOnClickListener {
val message: String = binding.textSend.text.toString()
if(message.isNotEmpty()){
sendMessage(userID,recID,message)
binding.textSend.text.clear()
}
else{
Toast.makeText(this,"You can't send empty message", Toast.LENGTH_SHORT).show()
}
}
binding.gps.setOnClickListener {
val uri = "http://maps.google.com/maps?daddr="
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(uri))
intent.setPackage("com.google.android.apps.maps")
startActivity(intent)
}
}
private fun sendMessage(sender: String, rec: String, message: String){
val db = Firebase.firestore
val time: FieldValue = FieldValue.serverTimestamp()
val msg = hashMapOf(
"userID" to sender,
"recID" to rec,
"message" to message,
"time" to time,
"roomID" to roomID,
"isSeen" to false
)
db.collection("chats").document(roomID).collection("messages").document().set(msg,SetOptions.merge())
}
private fun readMessages(userId: String, recId: String){
val rootRef = Firebase.firestore
rootRef.collection("chats").document(roomID).collection("messages").orderBy("time", Query.Direction.ASCENDING).addSnapshotListener(object : EventListener<QuerySnapshot?>
{
override fun onEvent(#Nullable documentSnapshots: QuerySnapshot?, #Nullable e: FirebaseFirestoreException?)
{
if (e != null)
{
Log.e(TAG, "onEvent: Listen failed.", e)
return
}
chat.clear()
if (documentSnapshots != null)
{
for (queryDocumentSnapshots in documentSnapshots)
{
val msg = queryDocumentSnapshots.toObject(Message::class.java)
if (msg.recID == recId && msg.userID == userId || msg.recID == userId && msg.userID == recId)
{
chat.add(msg)
}
if(msg.recID.equals(userID).and(msg.userID.equals(recID))){
rootRef.collection("chats").document(roomID).collection("messages").document().update("isSeen",true)
}
messageAdapter = MessageAdapter(chat)
binding.recyclerView.adapter = messageAdapter
}
}
}
})
}
}
Adapter Class
class MessageAdapter(private val MessageList:ArrayList<Message>):RecyclerView.Adapter<MessageAdapter.MessageViewHolder>() {
private val left = 0
private val right = 1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MessageViewHolder {
return if(viewType==right){
val view1 = LayoutInflater.from(parent.context).inflate(R.layout.chat_sender_item,parent,false)
MessageViewHolder(view1)
}else{
val view2 = LayoutInflater.from(parent.context).inflate(R.layout.chat_receiver_item,parent,false)
MessageViewHolder(view2)
}
}
override fun onBindViewHolder(holder: MessageViewHolder, position: Int) {
val message:Message = MessageList[position]
holder.showMessage.text = message.message
if(position==MessageList.size-1){
if(message.isSeen)
{
holder.textSeen.text = "Seen"
}else{
holder.textSeen.text = "Delivered"
}
}else{
holder.textSeen.visibility = View.GONE
}
}
override fun getItemCount(): Int {
return MessageList.size
}
class MessageViewHolder(itemView:View) : RecyclerView.ViewHolder(itemView){
val showMessage: TextView = itemView.findViewById(R.id.showMessage)
val textSeen: TextView = itemView.findViewById(R.id.textSeen)
}
override fun getItemViewType(position: Int): Int {
val userID = FirebaseAuth.getInstance().currentUser!!.uid
return if(MessageList[position].userID==userID)
{
right
}else
{
left
}
}
}
Model Class
package com.aarondcosta99.foodreuseapp.model
data class Message(var userID:String? = "",var message:String? = "",var recID:String? = "",var isSeen:Boolean=false)
Firestore
This won't work:
rootRef.collection("chats").document(roomID).collection("messages").document().update("isSeen",true)
The document() call without any arguments creates a reference to a new non-existing document, which you then try to update. But update() only works when a document already exists, you can't use update() to create a document, so this code ends up doing nothing.
To update a document, you need to specify the complete path to that document. The fact that you need to update a lot of documents makes no difference to that fact, it just means you'll need to paths to a lot of documents.
As far as I can tell, you are trying to update the document that you read in documentSnapshots, which means you already have the DocumentReference handy and can update it with:
queryDocumentSnapshots.reference.update("isSeen",true)

"Job was Cancelled" Android Kotlin Coroutines

In my app, there is an EditText that shows date picker dialog and also a spinner. The spinner item contains data from database that is filled after date is set.
It was working normal until in some occasion that I couldn't replicate the error, it shows "Job was cancelled" in Logcat. And when it happened, the spinner is empty.
Here is my fragment code
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
private var isMandatory : Boolean? = true
class AddReportFragment : Fragment(), AdapterView.OnItemSelectedListener {
// TODO: Rename and change types of parameters
private var param1: String? = null
private var param2: String? = null
private lateinit var binding: FragmentAddReportBinding
private lateinit var viewModel: AddReportViewModel
private lateinit var sptransactionType: Spinner
private lateinit var spMandatory: Spinner
private lateinit var spNonMandatory: Spinner
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
binding = FragmentAddReportBinding.inflate(inflater)
viewModel = ViewModelProvider(this).get(AddReportViewModel::class.java)
val spBookName : Spinner = binding.spBookName
spMandatory = binding.spMandatory
spNonMandatory = binding.spNonMandatory
sptransactionType = binding.sptransactionType
viewModel.bookName.observe(viewLifecycleOwner, Observer {
it?.let {
var string : String = ""
val listArr = arrayListOf<String>()
for (i in 0 .. it.size - 1){
string = it.get(i).bookName
listArr.add(string)
}
ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, listArr)
.also { adapter ->
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spBookName.adapter = adapter
}
}
})
binding.etPeriode.setOnFocusChangeListener { v, hasFocus ->
if(v.hasFocus()){
val showDialog = ShowDatePickerDialog()
showDialog.show(requireFragmentManager(), "Show Date Picker")
}
}
sptransactionType.onItemSelectedListener = this
spBookName.onItemSelectedListener = this
return binding.root
}
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
if(parent!!.id == R.id.spBookName) {
val selected = parent.selectedItem
viewModel.getBookType(selected.toString())
viewModel.bookType.observe(viewLifecycleOwner, Observer {
it?.let {
if (it.get(0).bookType == "Mandatory") {
ArrayAdapter.createFromResource(requireContext(), R.array.transactionType, android.R.layout.simple_spinner_item)
.also { adapter ->
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
sptransactionType.adapter = adapter
}
isMandatory = true
} else if (it.get(0).bookType == "Non Mandatory") {
ArrayAdapter.createFromResource(requireContext(), R.array.transactionType, android.R.layout.simple_spinner_item)
.also { adapter ->
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
sptransactionType.adapter = adapter
}
isMandatory = false
}
viewModel.resetBookType()
}
})
viewModel.getMandatoryName(selected.toString())
viewModel.getNonMandatoryName(selected.toString())
viewModel.mandatoryName.observe(viewLifecycleOwner, Observer {
it?.let {
var string : String = ""
val listArr = arrayListOf<String>()
for(i in 0 .. it.size -1){
string = it.get(i).name
listArr.add(string)
}
ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, listArr)
.also { adapter ->
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spMandatory.adapter = adapter
}
}
})
viewModel.nonMandatoryName.observe(viewLifecycleOwner, Observer {
it?.let {
var string : String = ""
val listArr = arrayListOf<String>()
for(i in 0 .. it.size -1){
string = it.get(i).name
listArr.add(string)
}
ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, listArr)
.also { adapter ->
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spNonMandatory.adapter = adapter
}
}
})
}else if (parent.id == R.id.sptransactionType){
val selected = parent.selectedItem
if(selected == "Plus" && isMandatory == true) {
binding.spMandatory.visibility = View.VISIBLE
binding.spNonMandatory.visibility = View.GONE
binding.etNameNonMandatory.visibility = View.GONE
binding.etNameNonMandatory.setText("")
}else if(selected == "Minus" && isMandatory == true){
binding.spMandatory.visibility = View.GONE
binding.spNonMandatory.visibility = View.VISIBLE
binding.etNameNonMandatory.visibility = View.GONE
binding.etNameNonMandatory.setText("")
}else if(selected == "Minus" && isMandatory == false){
binding.spMandatory.visibility = View.GONE
binding.spNonMandatory.visibility = View.GONE
binding.etNameNonMandatory.visibility = View.GONE
binding.etNameNonMandatory.setText("")
}else if(selected == "Plus" && isMandatory == false){
binding.spMandatory.visibility = View.GONE
binding.spNonMandatory.visibility = View.GONE
binding.etNameNonMandatory.visibility = View.VISIBLE
binding.etNameNonMandatory.setText("")
}else{
binding.spMandatory.visibility = View.GONE
binding.spNonMandatory.visibility = View.GONE
binding.etNameNonMandatory.visibility = View.GONE
binding.etNameNonMandatory.setText("")
}
}
}
override fun onNothingSelected(parent: AdapterView<*>?) {
}
class ShowDatePickerDialog() : DialogFragment(), DatePickerDialog.OnDateSetListener{
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val calendar = Calendar.getInstance()
val day = calendar.get(Calendar.DAY_OF_MONTH)
val month = calendar.get(Calendar.MONTH)
val year = calendar.get(Calendar.YEAR)
return DatePickerDialog(requireContext(), this, year, month, day)
}
override fun onDateSet(view: DatePicker?, year: Int, month: Int, dayOfMonth: Int) {
val periode = requireActivity().findViewById<EditText>(R.id.etPeriode)
val spNonMandatory: Spinner = requireActivity().findViewById(R.id.spNonMandatory)
val spMandatory: Spinner = requireActivity().findViewById(R.id.spMandatory)
val spBookName: Spinner = requireActivity().findViewById(R.id.spBookName)
val viewModel = ViewModelProvider(this).get(AddReportViewModel::class.java)
val month = month + 1
val givenDate = "$dayOfMonth $month $year"
val givenDateFormat = SimpleDateFormat("dd MM yyyy")
val givenDateParse = givenDateFormat.parse(givenDate)
val resultGivenDate = givenDateParse.time
val givenMonth = "$month"
val givenMonthFormat = SimpleDateFormat("MM")
val givenMonthParse = givenMonthFormat.parse(givenMonth)
val resultGivenMonth = givenMonthParse.time
val monthNow = SimpleDateFormat("MM").format(Date())
val dateFormat = SimpleDateFormat("MM")
val toMil = dateFormat.parse(monthNow)
val result = toMil.time
if(resultGivenMonth == result){
viewModel.getMandatoryName(spBookName.selectedItem.toString())
viewModel.getNonMandatoryName(spBookName.selectedItem.toString())
viewModel.mandatoryName.observe(this, Observer {
it?.let {
var string : String = ""
val listArr = arrayListOf<String>()
for(i in 0 .. it.size -1){
string = it.get(i).name
listArr.add(string)
}
ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, listArr)
.also { adapter ->
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spMandatory.adapter = adapter
adapter.notifyDataSetChanged()
}
}
})
viewModel.nonMandatoryName.observe(this, Observer {
it?.let {
var string : String = ""
val listArr = arrayListOf<String>()
for(i in 0 .. it.size -1){
string = it.get(i).name
listArr.add(string)
}
ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, listArr)
.also { adapter ->
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spNonMandatory.adapter = adapter
adapter.notifyDataSetChanged()
}
}
})
}else if(resultGivenMonth != result){
viewModel.getAll(spBookName.selectedItem.toString())
viewModel.getAll.observe(this, Observer {
it?.let {
var string : String = ""
val listArr = arrayListOf<String>()
for(i in 0 .. it.size -1){
string = it.get(i).warga
listArr.add(string)
}
ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, listArr)
.also { adapter ->
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spNonMandatory.adapter = adapter
adapter.notifyDataSetChanged()
}
ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, listArr)
.also { adapter ->
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spMandatory.adapter = adapter
adapter.notifyDataSetChanged()
}
}
})
// viewModel.resetGetAll()
}
periode.setText(SimpleDateFormat("dd MMM yyyy").format(resultGivenDate))
}
}
And ViewModel code
class AddReportViewModel : ViewModel() {
private val _bookName = MutableLiveData<List<GetBookName>>()
val bookName: LiveData<List<GetBookName>>
get() = _bookName
private val _mandatoryName = MutableLiveData<List<GetMandatoryName>>()
val mandatoryName: LiveData<List<GetMandatoryName>>
get() = _mandatoryName
private val _nonMandatoryName = MutableLiveData<List<GetNonMandatoryName>>()
val nonMandatoryName: LiveData<List<GetNonMandatoryName>>
get() = _nonMandatoryName
private val _bookType = MutableLiveData<List<GetBookType>>()
val bookType: LiveData<List<GetBookType>>
get() = _bookType
private val _getAll = MutableLiveData<List<GetAll>>()
val getAll: LiveData<List<GetAll>>
get() = _getAll
init {
getBookName()
}
private fun getBookName() {
viewModelScope.launch {
try {
val bookName = Ebook.retrofitService.getBookName()
_bookName.value = bookName
} catch (exception: Exception) {
_bookName.value = ArrayList()
}
}
}
fun getBookType(bookName: String){
viewModelScope.launch {
try {
val bookType = Ebook.retrofitService.getBookType(bookName)
_bookType.value = bookType
}catch (exception: Exception){
_bookType.value = ArrayList()
}
}
}
fun getMandatoryName(bookName: String){
viewModelScope.launch {
try{
val mandatoryName = Ebook.retrofitService.getMandatory(bookName)
_mandatoryName.value = mandatoryName
}catch (exception: Exception){
Log.e("Exception Message", exception.message.toString())
Log.e("Exception Message", exception.cause.toString())
}
}
}
fun getNonMandatoryName(bookName: String){
viewModelScope.launch {
try{
val nonMandatoryName = Ebook.retrofitService.getNonMandatory(bookName)
_nonMandatoryName.value = nonMandatoryName
}catch (exception: Exception){
Log.e("Exception Message", exception.message.toString())
Log.e("Exception Message", exception.cause.toString())
}
}
}
fun resetBookType(){
_bookType.value = null
}
fun getAll(bookName: String){
viewModelScope.launch {
try{
val getAll = Ekomplek.retrofitService.getAllBayar(bookName)
_getAll.value = getAll
}catch (exception: Exception){
Log.e("Exception Message", exception.message.toString())
}
}
}
fun resetGetAll(){
_getAll.value = null
}
fun resetDonaturDebetKredit(){
_mandatoryName.value = null
_nonMandatoryName.value = null
}
}
As you can see, here on override fun onDateSet(view: DatePicker?, year: Int, month: Int, dayOfMonth: Int), I'm doing a check whether month now is the same as the month that is selected from date picker dialog. If it's the same, the spinner is filled with a filtered data from database that is matched with the month. And if month now is not the same as the month that is selected, the spinner is filled with all data from database.
Rarely, after I set the date, it shows "Job was cancelled" and the spinner is blank. Most of the time it works, though.
Is there anyway I fix this?
P.S. the same coroutine is also used in other fragments. Even in class AddReportFragment, that coroutine is also used. Everything was fine. No "Job was cancelled". I don't know why sometimes it doesn't work on DatePickerDialog class

Any alternative to showing chat messages on android app

this is the chat functionality on my app but the messages aren't appearing on my app although it is getting updated in the firebase. when I hardcoded the values it is working. this is the conversation activity. I've used a chat fragment and adapter as well. But the messages are just not getting posted on the app. any alternative solutions or a solution for this would be nice.
private val firebaseDB = FirebaseFirestore.getInstance()
private val userId = FirebaseAuth.getInstance().currentUser?.uid
private val conversationAdapter = ConversationAdapter(arrayListOf(), userId)
private var chatId: String? = null
private var imageUrl: String? = null
private var otherUserId: String? = null
private var chatName: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_conversation)
chatId = intent.extras.getString(PARAM_CHAT_ID)
imageUrl = intent.extras.getString(PARAM_IMAGE_URL)
chatName = intent.extras.getString(chatName)
otherUserId = intent.extras.getString(PARAM_OTHER_USER_ID)
if (chatId.isNullOrEmpty() || userId.isNullOrEmpty()) {
Toast.makeText(this, "chat room error", Toast.LENGTH_LONG).show()
finish()
}
topNameTV.text = chatName
populateImage(this, imageUrl, topPhotoIV, R.drawable.default_user)
messagesRV.apply {
setHasFixedSize(false)
layoutManager = LinearLayoutManager(context)
adapter = conversationAdapter
}
firebaseDB.collection(DATA_CHATS)
.document(chatId!!)
.collection(DATA_CHAT_MESSAGES)
.orderBy(DATA_CHAT_MESSAGE_TIME)
.addSnapshotListener{ querySnapshot, firebaseFirestoreException ->
if(firebaseFirestoreException != null){
firebaseFirestoreException.printStackTrace()
return#addSnapshotListener
}else{
if (querySnapshot != null){
for(change in querySnapshot.documentChanges){
when(change.type){
DocumentChange.Type.ADDED -> {
val message = change.document.toObject(Convo::class.java)
if(message != null){
conversationAdapter.addMessage(message)
messagesRV.post {
messagesRV.smoothScrollToPosition(conversationAdapter.itemCount -1)
}
}
}
}
}
}
}
}
}
fun onSend(v: View) {
if (!messageET.text.isNullOrEmpty()){
val message = Convo(userId, messageET.text.toString(), System.currentTimeMillis())
firebaseDB.collection(DATA_CHATS).document(chatId!!)
.collection(DATA_CHAT_MESSAGES)
.document()
.set(message)
messageET.setText("",TextView.BufferType.EDITABLE)
}
}
companion object {
private val PARAM_CHAT_ID = "Chat id"
private val PARAM_IMAGE_URL = "Image url"
private val PARAM_OTHER_USER_ID = "Other user id"
private val PARAM_CHAT_NAME = "Chat name"
fun newIntent(context: Context?, chatId: String?, imageUrl: String?, otherUserId: String?
chatName: String?): Intent{
val intent = Intent(context,ConversationActivity::class.java)
intent.putExtra(PARAM_CHAT_ID,chatId)
intent.putExtra(PARAM_IMAGE_URL, imageUrl)
intent.putExtra(PARAM_OTHER_USER_ID, otherUserId)
intent.putExtra(PARAM_CHAT_NAME, chatName)
return intent
}
}
}

Android Kotlin Pusher Chatkit - error - Room membership required

I'm trying to integrate chatkit into my Android app grabbing portions of code from this getting started tutorial and this android-public-demo-app project on github and I am getting this error:
D/ChatRoomsActivity: on subscripetoRoomMultipart reason:: Room membership required.
The user is already a member of the room which is producing is an error according to the dashboard/console snippets which are shown at the bottom of this post. Currently the currentUser is: user id=username2-PCKid
Error occurs in ChatRoomAcitivity.kt at currentUser.subscribeToRoomMultipart. I included the ChatRoomListActivity and adapters for context.
Any and all help is appreciated. Please let me know if more context is required.
here is my ChatRoomListActivity.kt
class ChatRoomsListActivity : AppCompatActivity() {
val adapter = ChatRoomsListAdapter();
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat_room_list)
initRecyclerView()
initChatManager()
}
private fun initRecyclerView() {
recycler_view.layoutManager = LinearLayoutManager(this#ChatRoomsListActivity)
recycler_view.adapter = adapter
}
private fun initChatManager() {
val chatManager = ChatManager(
instanceLocator = "************",
userId = "username2-PCKid",
dependencies = AndroidChatkitDependencies(
tokenProvider = ChatkitTokenProvider(
endpoint = "******************",
userId = "username2-PCKid"
)
)
)
chatManager.connect(listeners = ChatListeners(
)
, callback = { result ->
when (result) {
is Result.Success -> {
// We have connected!
Log.d(AppActivityTags.chatRoomsListActivityTAG, "chatManager connected!")
val currentUser = result.value
AppController.currentUser = currentUser
Log.d(AppActivityTags.chatRoomsListActivityTAG, "user: " + currentUser + " is logged in to chatkit")
val userJoinedRooms = ArrayList<Room>()
for (x in currentUser.rooms) {
adapter.addRoom(x)
recycler_view.smoothScrollToPosition(0)
}
adapter.notifyDataSetChanged()
Log.d(AppActivityTags.chatRoomsListActivityTAG, "joined rooms.size: " + userJoinedRooms.size.toString());
adapter.setInterface(object : ChatRoomsListAdapter.RoomClickedInterface {
override fun roomSelected(room: Room) {
Log.d(AppActivityTags.chatRoomsListActivityTAG, "Room clicked!")
if (room.memberUserIds.contains("username2-PCKid")) {
// if (room.memberUserIds.contains(currentUser.id)) { <-- OG code
// user already belongs to this room
roomJoined(room)
Log.d("roomSelected", "user already belongs to this room: " + roomJoined(room))
} else {
currentUser.joinRoom(
roomId = room.id,
callback = { result ->
when (result) {
is Result.Success -> {
// Joined the room!
roomJoined(result.value)
}
is Result.Failure -> {
Log.d(AppActivityTags.chatRoomsListActivityTAG, result.error.toString())
}
}
}
)
}
}
})
}
is Result.Failure -> {
// Failure
Log.d(AppActivityTags.chatRoomsListActivityTAG, "ChatManager connection failed"
+ result.error.toString())
}
}
})
}
private fun roomJoined(room: Room) {
val intent = Intent(this#ChatRoomsListActivity, ChatRoomActivity::class.java)
Log.d(AppActivityTags.chatRoomsListActivityTAG, "function roomJoined activated")
intent.putExtra("room_id", room.id)
intent.putExtra("room_name", room.name)
startActivity(intent)
}
}
here is my ChatRoomListAdapter.kt
class ChatRoomsListAdapter: RecyclerView.Adapter<ChatRoomsListAdapter.ViewHolder>() {
private var list = ArrayList<Room>()
private var roomClickedInterface: RoomClickedInterface? = null
fun addRoom(room:Room){
list.add(room);
Log.d(AppActivityTags.chatRoomsListAdapterTAG, "Room name: " + room.name)
Log.d(AppActivityTags.chatRoomsListAdapterTAG, "Room id: " + room.id)
Log.d(AppActivityTags.chatRoomsListAdapterTAG, "Room memberUserIds: " + room.memberUserIds)
Log.d(AppActivityTags.chatRoomsListAdapterTAG, "Room isPrivate: " + room.isPrivate)
}
fun setInterface(roomClickedInterface:RoomClickedInterface){
this.roomClickedInterface = roomClickedInterface
}
override fun getItemCount(): Int {
return list.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(
android.R.layout.simple_list_item_1,
parent,
false
)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.roomName.text = list[position].name
val context = holder.itemView.context
holder.itemView.setOnClickListener {
room = list[position]
val intent = Intent(context, ChatRoomActivity::class.java)
Log.d(AppActivityTags.chatRoomsListActivityTAG, "function roomJoined activated")
intent.putExtra("room_id", room.id)
intent.putExtra("room_name", room.name)
context.startActivity(intent)
}
}
inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView), View.OnClickListener {
override fun onClick(p0: View?) {
roomClickedInterface?.roomSelected(list[adapterPosition])
Toast.makeText(itemView.context, "item was clicked", Toast.LENGTH_LONG).show()
val mContext = itemView.context
Log.d(AppActivityTags.chatRoomsListAdapterTAG, "Size of adapter: " + list.size.toString())
Log.d(AppActivityTags.chatRoomsListAdapterTAG, roomName.toString() + " roomName clicked")
}
var roomName: TextView = itemView.findViewById(android.R.id.text1)
init {
itemView.setOnClickListener(this)
}
}
interface RoomClickedInterface{
fun roomSelected(room:Room)
}
}
here is my ChatRoomActivity.kt
class ChatRoomActivity : AppCompatActivity() {
lateinit var adapter:ChatRoomAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat_room)
supportActionBar!!.title = intent.getStringExtra("room_name")
adapter = ChatRoomAdapter()
setUpRecyclerView()
val currentUser = AppController.currentUser
val roomId = intent.getStringExtra("room_id")
currentUser.subscribeToRoomMultipart(
roomId = roomId,
listeners = RoomListeners(
onMultipartMessage = { message ->
Log.d("TAG",message.toString())
// com.pusher.chatkit.messages.multipart.Message
runOnUiThread(Runnable{
adapter.addMessage(message)
recycler_view.layoutManager?.scrollToPosition(adapter.itemCount -1)
})
},
onErrorOccurred = { error ->
Log.d(AppActivityTags.chatRoomActivityTAG, "error.reason: " + error.reason)
Log.d(AppActivityTags.chatRoomActivityTAG, "currentuser.rooms: " + currentUser.rooms)
}
),
messageLimit = 100, // Optional
callback = { subscription ->
// Called when the subscription has started.
// You should terminate the subscription with subscription.unsubscribe()
// when it is no longer needed
}
)
button_send.setOnClickListener {
if (edit_text.text.isNotEmpty()){
currentUser.sendSimpleMessage(
roomId = roomId,
messageText = edit_text.text.toString(),
callback = { result -> //Result<Int, Error>
when (result) {
is Result.Success -> {
runOnUiThread {
edit_text.text.clear()
hideKeyboard()
}
}
is Result.Failure -> {
Log.d(AppActivityTags.chatRoomActivityTAG, "error # button_send.setOnclick: " + result.error.toString())
}
}
}
)
}
}
}
private fun hideKeyboard() {
val imm = this.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
var view = this.currentFocus
if (view == null) {
view = View(this)
}
imm.hideSoftInputFromWindow(view.windowToken, 0)
}
private fun setUpRecyclerView() {
recycler_view.layoutManager= LinearLayoutManager(this#ChatRoomActivity)
recycler_view.adapter = adapter
}
}
here is my ChatRoomAdapter.kt
class ChatRoomAdapter: RecyclerView.Adapter<ChatRoomAdapter.ViewHolder>() {
private var list = ArrayList<Message>()
fun addMessage(message: Message){
list.add(message)
notifyDataSetChanged()
}
override fun getItemCount(): Int {
return list.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.custom_chat_row,parent,false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val inlineMessage: Payload.Inline = list[position].parts[0].payload as Payload.Inline
holder.userName.text = list[position].sender.name
holder.message.text = inlineMessage.content
}
inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
var userName: TextView = itemView.findViewById(R.id.text_user_name)
var message: TextView = itemView.findViewById(R.id.chat_message)
}
}
I think I know what happened.
I think what happened was I created a room from the pusher-chat kit dashboard and then tried to sign in as them. and then enter the room as them. I was able to see my chatroomlists that they were affiliated with however I think that since I created the chatroom from the dashboard, it thought I was someone else.
Long story short, it works if I create the room from my android emulator and then go to the room. if I create the room from the dashboard and try to join, it doesn't seem to work.

RecyclerView list item does not show when entries are made to the Room database

I am trying to rewrite an existing app in Kotlin for the purpose of learning and getting used to the language. The app allows the user to enter and modify entries and each entry is hosted in a RecyclerView list with a custom list item. Although entries are successfully added to the database (confirming that with Toast messages), there isn't a list item present for the said entry.
This is my adapter:
class EntryAdapter : androidx.recyclerview.widget.ListAdapter<Entry, EntryAdapter.ViewHolder>(DIFF_CALLBACK){
private var listener: OnItemLongClickListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.recyclerview_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int){
val currentEntry = getItem(position)
holder.hint.text = currentEntry.hint
holder.username.text = currentEntry.username
holder.password.text = currentEntry.password
}
fun getEntryAt(position: Int): Entry{return getItem(position)}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
val username: TextView = itemView.findViewById(R.id.username_display)
val password: TextView = itemView.findViewById(R.id.password_display)
val hint: TextView = itemView.findViewById(R.id.hint_display)
init {
itemView.setOnLongClickListener{
val position = adapterPosition
if (listener != null && position != RecyclerView.NO_POSITION){listener!!.onItemLongClick(getItem(position))}
true
}
}
}
interface OnItemLongClickListener{fun onItemLongClick(entry: Entry)}
fun setOnItemLongClickListener(listener: OnItemLongClickListener){this.listener = listener}
companion object{
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Entry>(){
override fun areItemsTheSame(oldItem: Entry, newItem: Entry): Boolean{return oldItem.id == newItem.id}
override fun areContentsTheSame(oldItem: Entry, newItem: Entry): Boolean{return oldItem.username == newItem.username && oldItem.hint == newItem.hint && oldItem.password == newItem.password}
}
}
}
This is my AddEditEntry.kt activity. The way it works is that when the user wishes to make an entry, he/she clicks on the FAB button which invokes this activity. This activity is where the user enters the entry and adds it to the database (and by extension, the RecyclerView) by clicking the saveEntry button:
class AddEditEntryActivity : AppCompatActivity() {
private var usernameEditText: EditText? = null
private var passwordEditText: EditText? = null
private var hintEditText: EditText? = null
private var passwordABCD: CheckBox? = null
private var passwordabcd: CheckBox? = null
private var password0123: CheckBox? = null
private var passwordSymbols: CheckBox? = null
private var radio4: RadioButton? = null
private var radio8: RadioButton? = null
private var radio12: RadioButton? = null
private var radio16: RadioButton? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_addedit_entry)
usernameEditText = findViewById(R.id.username_field)
passwordEditText = findViewById(R.id.password_field)
hintEditText = findViewById(R.id.hint_field)
passwordABCD = findViewById(R.id.upp_checkbox)
passwordabcd = findViewById(R.id.low_checkbox)
password0123 = findViewById(R.id.num_checkbox)
passwordSymbols = findViewById(R.id.sym_checkbox)
radio4 = findViewById(R.id.four)
radio8 = findViewById(R.id.eight)
radio12 = findViewById(R.id.twelve)
radio16 = findViewById(R.id.sixteen)
val generatePassword = findViewById<Button>(R.id.btn_password_generate)
val saveEntry = findViewById<Button>(R.id.btn_save)
val intent = intent
if (intent.hasExtra(EXTRA_ID)) {
title = getString(R.string.edit_entry)
saveEntry.setText(R.string.update_entry)
usernameEditText!!.setText(getIntent().getStringExtra(EXTRA_USERNAME))
passwordEditText!!.setText(getIntent().getStringExtra(EXTRA_PASSWORD))
hintEditText!!.setText(getIntent().getStringExtra(EXTRA_HINT))
}
else {title = "Add Entry"}
Objects.requireNonNull<ActionBar>(supportActionBar).setHomeAsUpIndicator(R.drawable.ic_close_white_24dp)
generatePassword.setOnClickListener { passwordEditText!!.setText(generatedPassword()) }
saveEntry.setOnClickListener {
val data = Intent()
data.putExtra(EXTRA_USERNAME, usernameEditText!!.text.toString())
data.putExtra(EXTRA_HINT, hintEditText!!.text.toString())
data.putExtra(EXTRA_PASSWORD, passwordEditText!!.text.toString())
val id = getIntent().getIntExtra(EXTRA_ID, -1)
if (id != -1) {data.putExtra(EXTRA_ID, id)}
setResult(Activity.RESULT_OK, data)
finish()
Toast.makeText(this, "data.putExtra() from AddEditEntryActivity", Toast.LENGTH_SHORT).show()
Toast.makeText(this, usernameEditText!!.text.toString(), Toast.LENGTH_SHORT).show()
Toast.makeText(this, hintEditText!!.text.toString(), Toast.LENGTH_SHORT).show()
Toast.makeText(this, passwordEditText!!.text.toString(), Toast.LENGTH_SHORT).show()
}
}
private fun generatedPassword(): String? {
var length = 0
val generatedString = StringBuilder()
val rand = Random()
val capitalLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
val lowercaseLetters = "abcdefghijklmnopqrstuvwxyz"
val numbers = "0123456789"
val characters = "!##$%^&*()"
if (radio4!!.isChecked) {length = 4}
else if (radio8!!.isChecked) {length = 8}
else if (radio12!!.isChecked) {length = 12}
else if (radio16!!.isChecked) {length = 16}
var totalCharacters = ""
if (passwordABCD!!.isChecked) {totalCharacters += capitalLetters}
if (passwordabcd!!.isChecked) {totalCharacters += lowercaseLetters}
if (password0123!!.isChecked) {totalCharacters += numbers}
if (passwordSymbols!!.isChecked) {totalCharacters += characters}
if (!totalCharacters.trim { it <= ' ' }.isEmpty() && length > 0) {
for (i in 0 until length) {generatedString.append(totalCharacters[rand.nextInt(totalCharacters.length)])}
return generatedString.toString()
}
else {Toast.makeText(this, "Not a valid password!", Toast.LENGTH_SHORT).show()}
return null
}
companion object {
val EXTRA_USERNAME = "com.ozbek.cryptpass.EXTRA_USERNAME"
val EXTRA_HINT = "com.ozbek.cryptpass.EXTRA_HINT"
val EXTRA_PASSWORD = "com.ozbek.cryptpass.EXTRA_PASSWORD"
val EXTRA_ID = "com.ozbek.cryptpass.EXTRA_ID"
}
}
And this is the MainActivity.kt file where the user enters new entries. The first if block in onActivityResult() is the code that retrieves the entries from AddEditEntry.kt file and adds it to the entity class:
class MainActivity : AppCompatActivity(), LifecycleOwner {
private lateinit var recyclerView: RecyclerView
internal lateinit var adapter: EntryAdapter
private lateinit var floatingActionButton: FloatingActionButton
private lateinit var layoutManager: RecyclerView.LayoutManager
private lateinit var addEditEntryActivity: AddEditEntryActivity
internal lateinit var entry: Entry
private lateinit var viewModel: EntryViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val ctx = this.applicationContext
val sentryDsn = "https://93f06d5090d646ac9443a3fc531fc5de#sentry.io/1454826:port/1?options"
Sentry.init(sentryDsn, AndroidSentryClientFactory(ctx))
Sentry.init(AndroidSentryClientFactory(ctx))
viewModel = ViewModelProviders.of(this).get(EntryViewModel::class.java)
viewModel.allEntries.observe(this, Observer { entries -> adapter.submitList(entries) })
adapter = EntryAdapter()
layoutManager = LinearLayoutManager(this)
recyclerView = findViewById<RecyclerView>(R.id.userpass_recyclerview).apply{
layoutManager = layoutManager
adapter = adapter
}
addEditEntryActivity = AddEditEntryActivity()
entry = Entry()
floatingActionButton = findViewById<FloatingActionButton>(R.id.generate_fab)
floatingActionButton.setOnClickListener {
val intent = Intent(this#MainActivity, AddEditEntryActivity::class.java)
Toast.makeText(this, "AddEditActivity started", Toast.LENGTH_SHORT).show()
startActivityForResult(intent, ADD_ENTRY_REQUEST)
}
ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT) {
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {return false}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {viewModel.delete(adapter.getEntryAt(viewHolder.adapterPosition))}
}).attachToRecyclerView(recyclerView)
adapter.setOnItemLongClickListener (object: EntryAdapter.OnItemLongClickListener {
override fun onItemLongClick(entry: Entry){
val intent = Intent(this#MainActivity, AddEditEntryActivity::class.java)
intent.putExtra(AddEditEntryActivity.EXTRA_ID, entry.id)
intent.putExtra(AddEditEntryActivity.EXTRA_USERNAME, entry.username)
intent.putExtra(AddEditEntryActivity.EXTRA_HINT, entry.hint)
intent.putExtra(AddEditEntryActivity.EXTRA_PASSWORD, entry.password)
startActivityForResult(intent, EDIT_ENTRY_REQUEST)
}
})
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val itemId = item.itemId
if (itemId == R.id.delete_all) {viewModel.deleteAll()}
return super.onOptionsItemSelected(item)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == ADD_ENTRY_REQUEST && resultCode == Activity.RESULT_OK) {
Toast.makeText(this, data?.getStringExtra(AddEditEntryActivity.EXTRA_USERNAME), Toast.LENGTH_SHORT).show()
Toast.makeText(this, data?.getStringExtra(AddEditEntryActivity.EXTRA_PASSWORD), Toast.LENGTH_SHORT).show()
Toast.makeText(this, data?.getStringExtra(AddEditEntryActivity.EXTRA_HINT), Toast.LENGTH_SHORT).show()
val username = Objects.requireNonNull<Intent>(data).getStringExtra(AddEditEntryActivity.EXTRA_USERNAME)
val password = Objects.requireNonNull<Intent>(data).getStringExtra(AddEditEntryActivity.EXTRA_PASSWORD)
val hint = Objects.requireNonNull<Intent>(data).getStringExtra(AddEditEntryActivity.EXTRA_HINT)
val entry = Entry(username, hint, password)
viewModel.insert(entry)
Toast.makeText(this, "Entry added!", Toast.LENGTH_SHORT).show()
} else if (requestCode == EDIT_ENTRY_REQUEST && resultCode == Activity.RESULT_OK) {
Toast.makeText(this, data!!.getIntExtra(AddEditEntryActivity.EXTRA_ID, -1), Toast.LENGTH_SHORT).show()
Toast.makeText(this, data.getStringExtra(AddEditEntryActivity.EXTRA_USERNAME), Toast.LENGTH_SHORT).show()
Toast.makeText(this, data.getStringExtra(AddEditEntryActivity.EXTRA_PASSWORD), Toast.LENGTH_SHORT).show()
Toast.makeText(this, data.getStringExtra(AddEditEntryActivity.EXTRA_HINT), Toast.LENGTH_SHORT).show()
val id = Objects.requireNonNull<Intent>(data).getIntExtra(AddEditEntryActivity.EXTRA_ID, -1)
if (id == -1) {
Toast.makeText(this, "Something went wrong", Toast.LENGTH_SHORT).show()
return
}
val username = Objects.requireNonNull<Intent>(data).getStringExtra(AddEditEntryActivity.EXTRA_USERNAME)
val password = Objects.requireNonNull<Intent>(data).getStringExtra(AddEditEntryActivity.EXTRA_PASSWORD)
val hint = Objects.requireNonNull<Intent>(data).getStringExtra(AddEditEntryActivity.EXTRA_HINT)
val entry = Entry(username, hint, password, id)
entry.id = id
viewModel.update(entry)
Toast.makeText(this, "Entry updated", Toast.LENGTH_SHORT).show()
} else {Toast.makeText(this, "Entry not added!", Toast.LENGTH_SHORT).show()}
}
companion object {
const val ADD_ENTRY_REQUEST = 1
const val EDIT_ENTRY_REQUEST = 2
}
}
I can add more code per request. This is the full Github repo.
Here is the problem:
recyclerView = findViewById<RecyclerView>(R.id.userpass_recyclerview).apply{
layoutManager = layoutManager
adapter = adapter
}
Idk what's going on but it seems like you're initializing them to themselves. Don't do that. Its good practice to keep your variable names distinct. Refactor the objects like this:
internal lateinit var entryAdapter: EntryAdapter
private lateinit var linearLayoutManager: RecyclerView.LayoutManager
And make the following changes in your onCreate():
recyclerView = findViewById<RecyclerView>(R.id.userpass_recyclerview).apply{
adapter = entryAdapter
layoutManager = linearLayoutManager
}

Categories

Resources