I have integrated mesibo chat and tried to integrate the mesibo push-notification but not receiving the notification. I had set up all the things related push notification and already success integrate with the backend also.
Not sure why i can't receive notification from mesibo related new chat and new call.
This is my code:
package com.project.bucynapp.ui.message
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import com.mesibo.api.Mesibo.*
import com.mesibo.calls.api.MesiboCall
import com.project.bucynapp.R
import com.project.bucynapp.models.*
import com.project.bucynapp.ui.message.adapter.ChatListAdapter
import com.project.bucynapp.ui.message.presenter.ChatListPresenter
import com.project.bucynapp.ui.message.view.ChatListView
import com.project.bucynapp.utils.*
import com.squareup.picasso.Picasso
import kotlinx.android.synthetic.main.activity_chat_list.*
import kotlinx.android.synthetic.main.app_bar.toolbar
import kotlinx.android.synthetic.main.app_bar_chat_list.*
import kotlinx.android.synthetic.main.loading_view.*
class ChatListActivity : AppCompatActivity(), View.OnClickListener, ChatListView,
MessageListener, ConnectionListener, SyncListener {
private lateinit var presenter: ChatListPresenter
private lateinit var chatListAdapter: ChatListAdapter
private var chatList: MutableList<ChatModel> = mutableListOf()
private val layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
private lateinit var readSession: ReadDbSession
private var userMesibo: MesiboAccountData? = null
var userProfile: UserProfile? = null
private var imageProfile: String? = null
private var nameProfile: String? = null
private var userId: Int = 0
private var email: String? = null
companion object {
private const val USER_ID = "user_id"
private const val IMAGE_PROFILE = "img_profile"
private const val NAME_PROFILE = "name_profile"
private val TAG = ChatListActivity::class.java.canonicalName
fun startActivity(
context: Context,
userId: Int?,
imageProfile: String,
nameProfile: String
) {
Intent(context, ChatListActivity::class.java).apply {
putExtra(USER_ID, userId)
putExtra(IMAGE_PROFILE, imageProfile)
putExtra(NAME_PROFILE, nameProfile)
context.startActivity(this)
}
}
}
private val message: String
get() = edtSendChat.text.toString()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat_list)
initToolbar()
userMesibo = ApplicationPrefs.get(Constant.MESIBO_USER)
presenter = ChatListPresenter(this)
userId = intent.getIntExtra(USER_ID, 0)
imageProfile = intent.getStringExtra(IMAGE_PROFILE)
nameProfile = intent.getStringExtra(NAME_PROFILE)
chatListAdapter = ChatListAdapter(chatList)
rvChatList.adapter = chatListAdapter
rvChatList.layoutManager = layoutManager
rvChatList.addItemDecoration(DefaultItemDecoration(spacing = 10.dp, includeEdge = true))
tvName.text = nameProfile
Picasso.get().load(imageProfile)
.error(R.drawable.com_facebook_profile_picture_blank_square)
.placeholder(R.drawable.com_facebook_profile_picture_blank_square).into(imgProfile)
presenter.getEmailUser(userId)
}
private fun initToolbar() {
setSupportActionBar(toolbar)
val backArrow = ContextCompat.getDrawable(this, R.drawable.ic_back_black)
supportActionBar?.setHomeAsUpIndicator(backArrow)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
supportActionBar?.setDisplayShowTitleEnabled(false)
tvSeeProfile.setOnClickListener(this)
imgCalling.setOnClickListener(this)
imgVideoCall.setOnClickListener(this)
imgSendChat.setOnClickListener(this)
tilChat.setStartIconOnClickListener {
NotMatchActivity.startActivity(this)
}
}
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
override fun getChatListSuccess(response: MutableList<DataAttributes>) {
}
override fun getChatListFailed(message: String) {
}
override fun successPost(response: SuccessModel?) {
}
override fun failedPost(message: String) {
shortToast(message)
}
override fun onGetEmailUser(data: UserEmailResponse?) {
email = data?.email
startMesibo()
email?.let { loadFromDb(it) }
}
override fun onLoading(isShow: Boolean) {
if (isShow) {
rvChatList.gone()
loadingView.visible()
} else {
rvChatList.visible()
loadingView.gone()
}
}
override fun onNoData(msg: String?) {
shortToast(msg.toString())
}
override fun onBadRequest(msg: String?) {
shortToast(msg.toString())
}
override fun onUnauthorized(msg: String?) {
shortToast(msg.toString())
badToken()
}
override fun onClick(v: View?) {
when (v) {
imgSendChat -> if (message.isNotBlank()) onSendMessage()
tvSeeProfile -> {
UserProfileActivity.startActivity(this, userId)
}
imgCalling -> MesiboCall.getInstance().callUi(this, email, false)
imgVideoCall -> MesiboCall.getInstance().callUi(this, email, true)
}
}
override fun Mesibo_onMessage(p0: MessageParams?, p1: ByteArray?): Boolean {
val type: Int = if (p0?.isIncoming == true) 0 else 1
val msg = String(p1!!)
chatList.add(
ChatModel(
id = p0!!.mid,
msg = msg,
date = p0.ts,
type = type,
status = p0.status
)
)
chatListAdapter.notifyItemInserted(chatList.size)
layoutManager.scrollToPositionWithOffset(chatList.size - 1, 0)
return true
}
override fun Mesibo_onMessageStatus(p0: MessageParams?) {
chatList.find { p0?.mid == it.id }?.status = p0?.status
chatListAdapter.notifyDataSetChanged()
Log.i("TAG", "Mesibo_onMessageStatus: ${p0?.status}}")
}
override fun Mesibo_onActivity(p0: MessageParams?, p1: Int) {
Log.i("TAG", "Mesibo_onActivity: $p1")
when (p1) {
ACTIVITY_ONLINE -> {
imgDotOnline.visible()
imgDotOffline.gone()
Log.i(TAG, "MESIBO_ACTIVITY: activity on")
}
ACTIVITY_TYPING -> Log.i(TAG, "MESIBO_ACTIVITY: user typing")
else -> {
imgDotOnline.gone()
imgDotOffline.visible()
Log.i(TAG, "Mesibo_onActivity: $p1")
}
}
}
override fun Mesibo_onLocation(p0: MessageParams?, p1: Location?) {}
override fun Mesibo_onFile(p0: MessageParams?, p1: FileInfo?) {}
override fun Mesibo_onConnectionStatus(p0: Int) {
when (p0) {
STATUS_ONLINE -> {
setPushToken(ApplicationPrefs.tokenFcm)
tvConnectionStatus.gone()
Log.d(TAG, "MESIBO_CONNECTION_STATUS: online")
}
STATUS_OFFLINE -> {
tvConnectionStatus.gone()
Log.d(TAG, "MESIBO_CONNECTION_STATUS: offline")
}
STATUS_CONNECTING -> {
tvConnectionStatus.visible()
tvConnectionStatus.text = getString(R.string.connecting)
Log.d(TAG, "MESIBO_CONNECTION_STATUS: offline")
}
STATUS_CONNECTFAILURE, STATUS_NONETWORK -> {
tvConnectionStatus.visible()
tvConnectionStatus.text = getString(R.string.mesibo_connect_fail_msg)
}
else -> {
Log.d(TAG, "MESIBO_CONNECTION_STATUS: $p0")
Log.d(TAG, "MESIBO_TOKEN: ${userMesibo?.token}")
}
}
}
override fun Mesibo_onSync(p0: Int) {
if (p0 <= 0) return
readSession.read(p0)
}
private fun onSendMessage() {
val p = MessageParams()
p.profile = userProfile
p.peer = email
p.mid = random()
p.flag = FLAG_DELIVERYRECEIPT or FLAG_READRECEIPT
sendMessage(p, p.mid, message)
chatList.add(
ChatModel(
id = p.mid,
msg = edtSendChat.text.toString(),
date = getTimestamp(),
type = 1,
status = 0
)
)
chatListAdapter.notifyItemInserted(chatList.lastIndex)
layoutManager.scrollToPositionWithOffset(chatList.size - 1, 0)
edtSendChat.text?.clear()
}
private fun loadFromDb(address: String) {
setAppInForeground(this, ChatListActivity.hashCode(), true)
readSession = ReadDbSession(address, this)
readSession.enableReadReceipt(true)
readSession.enableCalls(false)
readSession.enableIncomingCalls(false)
readSession.enableMissedCalls(false)
readSession.enableOutgoingCalls(false)
readSession.enableFifo(true)
readSession.read(500)
}
private fun startMesibo() {
addListener(this)
setSecureConnection(true)
setAccessToken(userMesibo?.token)
setDatabase("db_mesibo", 0)
start()
userProfile = UserProfile()
userProfile?.address = email
setUserProfile(userProfile, true)
}
}
Any clue about how to resolve and debug?
this is my env:
Mesibo AP = 2.7.0
Build tools = 29.0.0
compile SDK versions = 30
appid = com.project.bucynapp
Thanks!
Have you tried the custom scripts or mesibo webhook to debug the issue? Have you referred to the Troubleshooting section in the mesibo push-notification document?
https://mesibo.com/documentation/api/push-notifications/
Unless you post logs and steps you have taken, we have no clue what is happening.
Related
I am trying to get a users profile pic to be displayed beside their booking on a card in my recyclerview. The profile pic is displaying in my nav drawer, a default one for non-google users, or the profile pic of the Google user. However imageUri appears empty when I call it and the images are not being stored in my firebase storage bucket. I have linked my firebase and implemented the relevant SDK.
Here is my model.
#Parcelize
data class BookModel(
// var id : Long = 0,
var uid: String? = "",
var name: String = "N/A",
var phoneNumber: String = "N/A",
var email: String? = "N/A",
var date: String = "",
var bike: Int = 0,
var profilepic: String = "",
/*var lat: Double = 0.0,
var longitude: Double = 0.0,
var zoom: Float = 0f,*/
var pickup: String = "",
var dropoff: String = "",
var price: Double = 20.0,
/*var amount: Int = 0*/
) : Parcelable
{
#Exclude
fun toMap(): Map<String, Any?> {
return mapOf(
// "id" to id,
"uid" to uid,
"name" to name,
"phoneNumber" to phoneNumber,
"email" to email,
"date" to date,
"bike" to bike,
"profilepic" to profilepic,
"pickup" to pickup,
"dropoff" to dropoff,
"price" to price
)
}
}
#Parcelize
data class Location(
var lat: Double = 0.0,
var longitude: Double = 0.0,
var zoom: Float = 0f,
var depot: String = "Waterford"
) : Parcelable
Here is my FirebaseImageManager
package com.wit.mad2bikeshop.firebase
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.net.Uri
import android.widget.ImageView
import androidx.lifecycle.MutableLiveData
import com.google.firebase.storage.FirebaseStorage
import com.google.firebase.storage.UploadTask
import com.squareup.picasso.MemoryPolicy
import com.squareup.picasso.Picasso
import com.wit.mad2bikeshop.utils.customTransformation
import timber.log.Timber
import java.io.ByteArrayOutputStream
import com.squareup.picasso.Target
object FirebaseImageManager {
var storage = FirebaseStorage.getInstance().reference
var imageUri = MutableLiveData<Uri>()
fun checkStorageForExistingProfilePic(userid: String) {
val imageRef = storage.child("photos").child("${userid}.jpg")
val defaultImageRef = storage.child("ic_book_nav_header.png")
imageRef.metadata.addOnSuccessListener { //File Exists
imageRef.downloadUrl.addOnCompleteListener { task ->
imageUri.value = task.result!!
}
//File Doesn't Exist
}.addOnFailureListener {
imageUri.value = Uri.EMPTY
}
}
fun uploadImageToFirebase(userid: String, bitmap: Bitmap, updating : Boolean) {
// Get the data from an ImageView as bytes
val imageRef = storage.child("photos").child("${userid}.jpg")
//val bitmap = (imageView as BitmapDrawable).bitmap
val baos = ByteArrayOutputStream()
lateinit var uploadTask: UploadTask
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos)
val data = baos.toByteArray()
imageRef.metadata.addOnSuccessListener { //File Exists
if(updating) // Update existing Image
{
uploadTask = imageRef.putBytes(data)
uploadTask.addOnSuccessListener { ut ->
ut.metadata!!.reference!!.downloadUrl.addOnCompleteListener { task ->
imageUri.value = task.result!!
}
}
}
}.addOnFailureListener { //File Doesn't Exist
uploadTask = imageRef.putBytes(data)
uploadTask.addOnSuccessListener { ut ->
ut.metadata!!.reference!!.downloadUrl.addOnCompleteListener { task ->
imageUri.value = task.result!!
}
}
}
}
fun updateUserImage(userid: String, imageUri : Uri?, imageView: ImageView, updating : Boolean) {
Picasso.get().load(imageUri)
.resize(200, 200)
.transform(customTransformation())
.memoryPolicy(MemoryPolicy.NO_CACHE)
.centerCrop()
.into(object : Target {
override fun onBitmapLoaded(bitmap: Bitmap?,
from: Picasso.LoadedFrom?
) {
Timber.i("DX onBitmapLoaded $bitmap")
uploadImageToFirebase(userid, bitmap!!,updating)
imageView.setImageBitmap(bitmap)
}
override fun onBitmapFailed(e: java.lang.Exception?,
errorDrawable: Drawable?) {
Timber.i("DX onBitmapFailed $e")
}
override fun onPrepareLoad(placeHolderDrawable: Drawable?) {}
})
}
fun updateDefaultImage(userid: String, resource: Int, imageView: ImageView) {
Picasso.get().load(resource)
.into(object : Target {
override fun onBitmapLoaded(bitmap: Bitmap?,
from: Picasso.LoadedFrom?
) {
Timber.i("DX onBitmapLoaded $bitmap")
uploadImageToFirebase(userid, bitmap!!,false)
imageView.setImageBitmap(bitmap)
}
override fun onBitmapFailed(e: java.lang.Exception?,
errorDrawable: Drawable?) {
Timber.i("DX onBitmapFailed $e")
}
override fun onPrepareLoad(placeHolderDrawable: Drawable?) {}
})
}
}
My FirebaseAuthManager...
package com.wit.mad2bikeshop.firebase
import android.app.Application
import androidx.lifecycle.MutableLiveData
import com.google.android.gms.auth.api.signin.GoogleSignIn
import com.google.android.gms.auth.api.signin.GoogleSignInAccount
import com.google.android.gms.auth.api.signin.GoogleSignInClient
import com.google.android.gms.auth.api.signin.GoogleSignInOptions
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseUser
import com.google.firebase.auth.GoogleAuthProvider
import com.wit.mad2bikeshop.R
import timber.log.Timber
class FirebaseAuthManager(application: Application) {
private var application: Application? = null
var firebaseAuth: FirebaseAuth? = null
var liveFirebaseUser = MutableLiveData<FirebaseUser>()
var loggedOut = MutableLiveData<Boolean>()
var errorStatus = MutableLiveData<Boolean>()
var googleSignInClient = MutableLiveData<GoogleSignInClient>()
init {
this.application = application
firebaseAuth = FirebaseAuth.getInstance()
if (firebaseAuth!!.currentUser != null) {
liveFirebaseUser.postValue(firebaseAuth!!.currentUser)
loggedOut.postValue(false)
errorStatus.postValue(false)
FirebaseImageManager.checkStorageForExistingProfilePic(
firebaseAuth!!.currentUser!!.uid)
}
configureGoogleSignIn()
}
fun login(email: String?, password: String?) {
firebaseAuth!!.signInWithEmailAndPassword(email!!, password!!)
.addOnCompleteListener(application!!.mainExecutor, { task ->
if (task.isSuccessful) {
liveFirebaseUser.postValue(firebaseAuth!!.currentUser)
errorStatus.postValue(false)
} else {
Timber.i("Login Failure: $task.exception!!.message")
errorStatus.postValue(true)
}
})
}
fun register(email: String?, password: String?) {
firebaseAuth!!.createUserWithEmailAndPassword(email!!, password!!)
.addOnCompleteListener(application!!.mainExecutor, { task ->
if (task.isSuccessful) {
liveFirebaseUser.postValue(firebaseAuth!!.currentUser)
errorStatus.postValue(false)
} else {
Timber.i("Registration Failure: $task.exception!!.message")
errorStatus.postValue(true)
}
})
}
fun logOut() {
firebaseAuth!!.signOut()
Timber.i( "firebaseAuth Signed out")
googleSignInClient.value!!.signOut()
Timber.i( "googleSignInClient Signed out")
loggedOut.postValue(true)
errorStatus.postValue(false)
}
private fun configureGoogleSignIn() {
val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestIdToken(application!!.getString(R.string.default_web_client_id))
.requestEmail()
.build()
googleSignInClient.value = GoogleSignIn.getClient(application!!.applicationContext,gso)
}
fun firebaseAuthWithGoogle(acct: GoogleSignInAccount) {
Timber.i( "DonationX firebaseAuthWithGoogle:" + acct.id!!)
val credential = GoogleAuthProvider.getCredential(acct.idToken, null)
firebaseAuth!!.signInWithCredential(credential)
.addOnCompleteListener(application!!.mainExecutor) { task ->
if (task.isSuccessful) {
// Sign in success, update with the signed-in user's information
Timber.i( "signInWithCredential:success")
liveFirebaseUser.postValue(firebaseAuth!!.currentUser)
} else {
// If sign in fails, display a message to the user.
Timber.i( "signInWithCredential:failure $task.exception")
errorStatus.postValue(true)
}
}
}
}
My Home activity
package com.wit.mad2bikeshop.ui.home
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.*
import com.google.firebase.auth.FirebaseUser
import com.squareup.picasso.Picasso
import com.wit.mad2bikeshop.R
import com.wit.mad2bikeshop.databinding.HomeBinding
import com.wit.mad2bikeshop.databinding.NavHeaderBinding
import com.wit.mad2bikeshop.firebase.FirebaseImageManager
import com.wit.mad2bikeshop.ui.auth.LoggedInViewModel
import com.wit.mad2bikeshop.ui.auth.Login
import com.wit.mad2bikeshop.utils.customTransformation
import timber.log.Timber
class Home : AppCompatActivity() {
private lateinit var drawerLayout: DrawerLayout
private lateinit var homeBinding: HomeBinding
private lateinit var navHeaderBinding: NavHeaderBinding
private lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var loggedInViewModel: LoggedInViewModel
private lateinit var headerView : View
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
homeBinding = HomeBinding.inflate(layoutInflater)
setContentView(homeBinding.root)
drawerLayout = homeBinding.drawerLayout
val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
val navHostFragment =
supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
appBarConfiguration = AppBarConfiguration(
setOf(
R.id.bookFragment, R.id.bookingListFragment
), drawerLayout
)
setupActionBarWithNavController(navController, appBarConfiguration)
val navView = homeBinding.navView
navView.setupWithNavController(navController)
initNavHeader()
}
public override fun onStart() {
super.onStart()
loggedInViewModel = ViewModelProvider(this).get(LoggedInViewModel::class.java)
loggedInViewModel.liveFirebaseUser.observe(this, Observer { firebaseUser ->
if (firebaseUser != null)
updateNavHeader(firebaseUser)
})
loggedInViewModel.loggedOut.observe(this, Observer { loggedout ->
if (loggedout) {
startActivity(Intent(this, Login::class.java))
}
})
}
private fun initNavHeader() {
Timber.i("DX Init Nav Header")
headerView = homeBinding.navView.getHeaderView(0)
navHeaderBinding = NavHeaderBinding.bind(headerView)
}
private fun updateNavHeader(currentUser: FirebaseUser) {
FirebaseImageManager.imageUri.observe(this, { result ->
if(result == Uri.EMPTY) {
Timber.i("DX NO Existing imageUri")
if (currentUser.photoUrl != null) {
//if you're a google user
FirebaseImageManager.updateUserImage(
currentUser.uid,
currentUser.photoUrl,
navHeaderBinding.navHeaderImage,
false)
}
else
{
Timber.i("DX Loading Existing Default imageUri")
FirebaseImageManager.updateDefaultImage(
currentUser.uid,
R.drawable.ic_book_nav_header,
navHeaderBinding.navHeaderImage)
}
}
else // load existing image from firebase
{
Timber.i("DX Loading Existing imageUri")
FirebaseImageManager.updateUserImage(
currentUser.uid,
FirebaseImageManager.imageUri.value,
navHeaderBinding.navHeaderImage, false)
}
})
navHeaderBinding.navHeaderEmail.text = currentUser.email
if(currentUser.displayName != null)
navHeaderBinding.navHeaderName.text = currentUser.displayName
}
override fun onSupportNavigateUp(): Boolean {
val navController = findNavController(R.id.nav_host_fragment)
return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}
fun signOut(item: MenuItem) {
loggedInViewModel.logOut()
//Launch Login activity and clear the back stack to stop navigating back to the Home activity
val intent = Intent(this, Login::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
}
}
My BookViewModel
package com.wit.mad2bikeshop.ui.book
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.google.firebase.auth.FirebaseUser
import com.wit.mad2bikeshop.firebase.FirebaseDBManager
import com.wit.mad2bikeshop.firebase.FirebaseImageManager
import com.wit.mad2bikeshop.model.BookManager
import com.wit.mad2bikeshop.model.BookModel
class BookViewModel : ViewModel() {
private val status = MutableLiveData<Boolean>()
val observableStatus: LiveData<Boolean>
get() = status
fun addBook(firebaseUser: MutableLiveData<FirebaseUser>,
booking: BookModel) {
status.value = try {
booking.profilepic = FirebaseImageManager.imageUri.value.toString()
FirebaseDBManager.create(firebaseUser,booking)
true
} catch (e: IllegalArgumentException) {
false
}
}
// fun updateBook(booking: BookModel){
// status.value = try {
// BookManager.update(booking)
// true
// } catch (e: IllegalArgumentException) {
// false
// }
// }
}
My BookAdapter
package com.wit.mad2bikeshop.adapters
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.net.toUri
import androidx.recyclerview.widget.RecyclerView
import com.squareup.picasso.MemoryPolicy
import com.squareup.picasso.Picasso
import com.wit.mad2bikeshop.R
import com.wit.mad2bikeshop.databinding.CardBookBinding
import com.wit.mad2bikeshop.model.BookModel
import com.wit.mad2bikeshop.utils.customTransformation
interface BookListener {
// fun onDeleteBooking(booking: BookModel)
// fun onUpdateBooking(booking: BookModel)
fun onBookingClick(booking: BookModel)
}
class BookAdapter constructor(
private var bookings: ArrayList<BookModel>,
private val listener: BookListener,
private val readOnly: Boolean)
: RecyclerView.Adapter<BookAdapter.MainHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainHolder {
val binding = CardBookBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return MainHolder(binding, readOnly)
}
override fun onBindViewHolder(holder: MainHolder, position: Int) {
val booking = bookings[holder.adapterPosition]
holder.bind(booking, listener)
}
fun removeAt(position: Int) {
bookings.removeAt(position)
notifyItemRemoved(position)
}
override fun getItemCount(): Int = bookings.size
inner class MainHolder(val binding: CardBookBinding,private val readOnly : Boolean) :
RecyclerView.ViewHolder(binding.root) {
val readOnlyRow = readOnly
fun bind(booking: BookModel, listener: BookListener) {
binding.root.tag = booking
binding.booking = booking
binding.imageIcon.setImageResource(R.mipmap.ic_launcher_round)
Picasso.get().load(booking.profilepic.toUri())
.resize(200, 200)
.transform(customTransformation())
.centerCrop()
.into(binding.imageIcon)
//
// binding.name.text = booking.name
// binding.phoneNumber.text = booking.phoneNumber
// binding.date.text = booking.date
binding.root.setOnClickListener { listener.onBookingClick(booking) }
// binding.buttonDelete.setOnClickListener { listener.onDeleteBooking(booking) }
// binding.buttonUpdate.setOnClickListener { listener.onUpdateBooking(booking) }
binding.executePendingBindings()
/* binding.imageIcon.setImageResource(R.mipmap.ic_launcher_round)*/
}
}
}
I believe the problem stems from the (FirebaseImageManager.imageUri.value.toString()) on my BookAdapter, as if I run a Timber.I statement it doesn't return anything at all. If I run Timber.i(FirebaseImageManager.imageUri.value.toString()+ "test"), it only returns test.
Here is a link to the full project https://github.com/foxxxxxxx7/MAD2bikeshop
Apologies if I wasn't clear enough, I am a novice.
I figured it out, I had to modify my rules on the firebase storage.
I am trying to change the value of debtCategory to either "Lender" or "Borrower". I have introduced two methods in my view model to update debtCategory. This is the logic:
If a user clicks addLender then updateLender method in view model is called which updates debtCategory with "Lender" value
If a user clicks addBorrower then updateBorrower method in view model is called which updates debtCategory with "Borrower" value
No error so far but the debtCatery is not updating accordingly. Help is highly appreciated.
Thanks in advance
This is my code:
1. VIEWMODEL
import android.content.Context
import android.text.Editable
import android.util.Log
import android.widget.ArrayAdapter
import android.widget.AutoCompleteTextView
import androidx.databinding.Bindable
import androidx.databinding.Observable
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.droid.f.debtmanagement.R
import com.droid.f.debtmanagement.models.Debtor
import com.droid.f.debtmanagement.repository.DebtManagementRepository
import com.droid.f.debtmanagement.util.Util
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
class DebtViewModel(private val debtManagementRepository: DebtManagementRepository)
: ViewModel(),Observable {
//all debtors
val allLenders = debtManagementRepository.getAllLenders()
val allBorrowers = debtManagementRepository.getAllBorrowers()
private val util = Util()
var debtCategory: String = ""
#Bindable
val clientName = MutableLiveData<String>()
#Bindable
val phoneNumber = MutableLiveData<String>()
#Bindable
val debtorAmount = MutableLiveData<String>()
#Bindable
val amountPaid = MutableLiveData<String>()
#Bindable
val period = MutableLiveData<String>()
private val datelend = MutableLiveData<String>()
private val dateToPay = MutableLiveData<String>()
private val debtStatus = MutableLiveData<String>()
private val timeBorrowed = MutableLiveData<String>()
private val remainingAmount = MutableLiveData<Double>()
init {
debtStatus.value = "Pending"
}
fun saveUpdate(){
val debtorName: String = clientName.value!!
val debtorPhone: String = phoneNumber.value!!
val debtAmount: Double = debtorAmount.value!!.toDouble()
val period: Int = monthsToDays()
val debtAmountPaid: Double = amountPaid.value!!.toDouble()
val debtorDateLend: String = dateBorrowedLend()
val debtorDateToPay: String = _dueDate()
val debtorStatus: String = checkProgress()
val debtTimeBorrowed: String = exactTimeBorrowed()
val remainingAmount: Double = remainingAmount()
val dtCategory: String = debtCategory
insertDebtor(
Debtor(
0, dtCategory, debtorName, debtorPhone, debtAmount, debtAmountPaid,period, debtorDateLend, debtorDateToPay, debtorStatus,
debtTimeBorrowed, remainingAmount
))
clearFields()
}
fun clearFields(){
clientName.value = null
phoneNumber.value = null
debtorAmount.value = null
period.value = null
amountPaid.value = null
}
fun updateLender(){
debtCategory = "Lender"
}
fun updateBorrower(){
debtCategory = "Borrower"
}
//Months to Date
private fun monthsToDays(): Int{
return util.monthsToDays(period.value!!.toInt())
}
//Date borroewed/lend
private fun dateBorrowedLend(): String{
return util.dateNow()
}
//Time borrowed/lend
private fun exactTimeBorrowed(): String{
return util.timeNow()
}
//Due date method
private fun _dueDate(): String{
return util.dueDate(util.monthsToDays(period.value!!.toInt()))
}
//remaining amount method
private fun remainingAmount(): Double{
return util.remainingDebt(debtorAmount.value!!.toDouble(), amountPaid.value!!.toDouble())
}
//check progress
private fun checkProgress(): String{
return util.checkProgress(debtorAmount.value!!.toDouble(), amountPaid.value!!.toDouble())
}
private fun insertDebtor(debtor: Debtor): Job = viewModelScope.launch {
debtManagementRepository.insertDebtor(debtor)
}
fun updateDebtor(debtor: Debtor): Job = viewModelScope.launch {
debtManagementRepository.updateDebtor(debtor)
}
fun deleteDebtor(debtor: Debtor): Job = viewModelScope.launch {
debtManagementRepository.deleteDebtor(debtor)
}
fun deleteAllDebtors(): Job = viewModelScope.launch {
debtManagementRepository.deleteAllDebtors()
}
override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
}
override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
}
}
2. MAINACTIVITY:
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
private var mDebtViewModel: DebtViewModel? = null
private lateinit var repository: DebtManagementRepository
private lateinit var factory: DebtManagementViewModelFactory
private lateinit var debtDatabase: DebtManagementDatabase
//initialize animation files
private val rotateOpen: Animation by lazy{ AnimationUtils.loadAnimation(this, R.anim.rotate_to_open_anim)}
private val rotateToClose: Animation by lazy { AnimationUtils.loadAnimation(this, R.anim.rotate_to_close_anim) }
private val fromBottom: Animation by lazy { AnimationUtils.loadAnimation(this, R.anim.from_bottom_anim) }
private val toBottom: Animation by lazy { AnimationUtils.loadAnimation(this, R.anim.to_bottom_anim) }
private var isOpen: Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
debtDatabase = DebtManagementDatabase.getDataBase(this)
repository = DebtManagementRepository(debtDatabase)
factory = DebtManagementViewModelFactory(repository)
mDebtViewModel = ViewModelProvider(this, factory).get(DebtViewModel::class.java)
binding.debtViewModel = mDebtViewModel
binding.lifecycleOwner = this
binding.addFab.setOnClickListener {
onAddFabClicked()
}
binding.addLender.setOnClickListener {
newDebtorActivity()
mDebtViewModel!!.updateLender()
}
binding.addBorrower.setOnClickListener {
newDebtorActivity()
mDebtViewModel!!.updateBorrower()
}
addTabsAndIntializePager()
}
private fun onAddFabClicked() {
setVisibility(isOpen)
setAnimation(isOpen)
isOpen = !isOpen
}
private fun setVisibility(isOpen: Boolean) {
if (!isOpen){
binding.addBorrower.visibility = View.VISIBLE
binding.addLender.visibility = View.VISIBLE
}else{
binding.addBorrower.visibility = View.INVISIBLE
binding.addLender.visibility = View.INVISIBLE
}
}
private fun setAnimation(isOpen: Boolean) {
if (!isOpen){
binding.addFab.startAnimation(rotateOpen)
binding.addLender.startAnimation(fromBottom)
binding.addBorrower.startAnimation(fromBottom)
}else{
binding.addFab.startAnimation(rotateToClose)
binding.addLender.startAnimation(toBottom)
binding.addBorrower.startAnimation(toBottom)
}
}
private fun newDebtorActivity() {
startActivity(Intent(this, EditActivity::class.java))
binding.addBorrower.visibility = View.INVISIBLE
binding.addLender.visibility = View.INVISIBLE
}
private fun addTabsAndIntializePager() {
binding.viewPager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle)
TabLayoutMediator(binding.debtManagementTab, binding.viewPager){tab, position->
when(position){
0->{
tab.text = "Borrowers"
}
1->{
tab.text = "Lenders"
}
}
}.attach()
}
}
instead of making debtCategory as a string why dont you change it to mutable live data
...
// from var debtCategory: String = ""
// change it to
val debtCategory = MutableLiveData<String>()
...
than make function to update it
fun updateDebtCategory(newValue: String){
debtCategory.value = newValue
}
And you can call it in your activity
viewModel.updateDebtCategory("Borrower")
//or
viewModel.updateDebtCategory("Lander")
Your Viewmodel class is OK.
Problem is on your 2 methods in MainActivity
Here, you are navigating your next activity before updating the value. You need to call mDebtViewModel!!.updateLender() and mDebtViewModel!!.updateBorrower() before calling newDebtorActivity()
here is your code
binding.addLender.setOnClickListener {
newDebtorActivity()
mDebtViewModel!!.updateLender()
}
binding.addBorrower.setOnClickListener {
newDebtorActivity()
mDebtViewModel!!.updateBorrower()
}
It should be
binding.addLender.setOnClickListener {
mDebtViewModel!!.updateLender()
newDebtorActivity()
}
binding.addBorrower.setOnClickListener {
mDebtViewModel!!.updateBorrower()
newDebtorActivity()
}
I am developing an audio player app that works well but I am facing one problem if I minimize the app it kill my foreground service. I don't know why it's happening can anyone suggest me any solution
MainActivity
import android.Manifest
import android.app.PendingIntent
import android.content.*
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.IBinder
import android.support.v4.media.MediaDescriptionCompat
import android.support.v4.media.MediaMetadataCompat
import android.support.v4.media.session.MediaSessionCompat
import android.text.Html
import android.util.Log
import android.view.WindowManager
import android.widget.SeekBar
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.brahmakumaris.fragment.*
import com.brahmakumaris.model.getDashboard.RecentSong
import com.brahmakumaris.service.MusicPlayerService
import com.brahmakumaris.service.MyService
import com.brahmakumaris.util.*
import com.google.android.exoplayer2.DefaultLoadControl
import com.google.android.exoplayer2.ExoPlaybackException
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.SimpleExoPlayer
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
import com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory
import com.google.android.exoplayer2.source.ConcatenatingMediaSource
import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector
import com.google.android.exoplayer2.ui.PlayerNotificationManager
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.util.Util
import kotlinx.android.synthetic.main.activity_main.*
import java.util.*
import kotlin.collections.ArrayList
class MainActivity : AppCompatActivity(), OnFragmentInteractionListener {
override fun onFragmentInteraction(screen: String, model: RecentSong, songList: ArrayList<RecentSong>, position: Int) {
MyLog.e(TAG, screen)
when (screen) {
getString(R.string.speakers) -> {
bottom_navigation.selectedItemId = R.id.nav_speaker
supportFragmentManager.switch(
newFrag = SpeakerPageFragment.newInstance(),
tag = getString(R.string.speakers)
)
}
getString(R.string.classes) -> {
bottom_navigation.selectedItemId = R.id.nav_class
supportFragmentManager.switch(
newFrag = ClassesPageFragment.newInstance(),
tag = getString(R.string.classes)
)
}
getString(R.string.songs) -> {
bottom_navigation.selectedItemId = R.id.nav_song
supportFragmentManager.switch(
newFrag = SongsPageFragment.newInstance(),
tag = getString(R.string.songs)
)
}
getString(R.string.play_song) -> {
if(!model.name.isNullOrBlank()) {
songPosition = position
mSongList = songList
MyLog.e(TAG, "======= ${getString(R.string.play_song)} ${model.name}")
isPlaying = false // to start new song
// play song
if (!lnPlayer.isVisible) {
lnPlayer.isVisible = true
}
titleTxt.setHtmlText(model.name)
if(mBound) {
startService()
mMusicPlayerService.playSong(mSongList,songPosition)
media_button.setImageResource(R.drawable.ic_pause_black_24dp)
}
setPlayPause(!mMusicPlayerService.isPlaying())
initSeekBar()
}
}
else -> {
}
}
}
private val TAG = javaClass.simpleName
private var isPlaying = false
private var mediaSource: ProgressiveMediaSource? = null
// notification
private lateinit var mediaSessionConnector: MediaSessionConnector
private lateinit var mediaSession: MediaSessionCompat
private lateinit var playerNotificationManager: PlayerNotificationManager
private var handler: Handler? = null
private val dashUrl = "http://www.panacherock.com/downloads/mp3/01_All_Tangled_Up.mp3"
var mSongList: ArrayList<RecentSong> = ArrayList()
var songPosition = 0
val MESSAGE_KEY = "message_key"
private lateinit var mMusicPlayerService:MusicPlayerService
private lateinit var mPlayer: SimpleExoPlayer
private var mBound = false
private val mServiceConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(componentName: ComponentName, iBinder: IBinder) {
val binder = iBinder as MusicPlayerService.MyServiceBinder
mMusicPlayerService = binder.getService()
mPlayer = mMusicPlayerService.getPlayerInstance()!!
mPlayer.addListener(playerListener)
mBound = true
MyLog.e(TAG,"onServiceConnected: ")
}
override fun onServiceDisconnected(componentName: ComponentName) {
mBound = false
MyLog.e(TAG,"onServiceDisconnected: ") // calles only in rare case if service destroy unexpectedly
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
if (savedInstanceState == null) {
supportFragmentManager.switch(
newFrag = DashboardPageFragment.newInstance(),
tag = getString(R.string.home)
)
}
getIntentData()
bottom_navigation.setOnNavigationItemSelectedListener { item ->
hideKeybord()
when(item.itemId) {
R.id.nav_home -> {
checkFragment()
supportFragmentManager.switch(
newFrag = DashboardPageFragment.newInstance(),
tag = getString(R.string.home)
)
true
}
R.id.nav_song -> {
checkFragment()
supportFragmentManager.switch(
newFrag = SongsPageFragment.newInstance(),
tag = getString(R.string.songs)
)
true
}
R.id.nav_speaker -> {
checkFragment()
supportFragmentManager.switch(
newFrag = SpeakerPageFragment.newInstance(),
tag = getString(R.string.speakers)
)
true
}
R.id.nav_class -> {
checkFragment()
supportFragmentManager.switch(
newFrag = ClassesPageFragment.newInstance(),
tag = getString(R.string.classes)
)
true
}
R.id.nav_search -> {
checkFragment()
supportFragmentManager.switch(
newFrag = SearchPageFragment.newInstance(),
tag = getString(R.string.search)
)
true
}
else -> false
}
}
bottom_navigation.setOnNavigationItemReselectedListener { } //disable reselection tab
media_button.setOnClickListener {
// startService()
setPlayPause(!isPlaying)
}
imgClose.setOnClickListener {
isPlaying = true // to stop song
playerNotificationManager.setPlayer(null)
setPlayPause(!isPlaying)
if (lnPlayer.isVisible) {
lnPlayer.isVisible = false
}
}
}
private fun getIntentData() {
if (intent!=null && intent.hasExtra("internet") && !intent.getBooleanExtra("internet", false)) {
this.changeFragment(
DownloadPageFragment.newInstance(), Constants.DOWNLOAD_TAG
)
}
}
private fun checkFragment() {
val fragment = getSupportFragmentManager().findFragmentByTag(Constants.SUB_CATEGORY_TAG)
if (fragment != null) {
getSupportFragmentManager().beginTransaction().remove(fragment).commit()
}
val fragment2 = getSupportFragmentManager().findFragmentByTag(Constants.DOWNLOAD_TAG)
if (fragment2 != null) {
getSupportFragmentManager().beginTransaction().remove(fragment2).commit()
}
}
private val playerListener by lazy {
object : Player.EventListener {
override fun onPlayerError(error: ExoPlaybackException) {
super.onPlayerError(error)
//onError(error)
}
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
super.onPlayerStateChanged(playWhenReady, playbackState)
when (playbackState) {
Player.STATE_BUFFERING -> Log.e(TAG,"STATE_BUFFERING")
Player.STATE_ENDED -> Log.e(TAG,"STATE_ENDED")
Player.STATE_IDLE -> Log.e(TAG,"STATE_IDLE")
Player.STATE_READY -> {
// setPlayPause(playWhenReady)
isPlaying = playWhenReady
mPlayer.setPlayWhenReady(playWhenReady)
if (!isPlaying) {
media_button.setImageResource(R.drawable.ic_play_arrow_black_24dp)
} else {
setProgress()
media_button.setImageResource(R.drawable.ic_pause_black_24dp)
}
if (playWhenReady) {
Log.e(TAG, "PlaybackStatus.PLAYING")
} else {
Log.e(TAG, "PlaybackStatus.PAUSED")
}
titleTxt.setHtmlText(mSongList[mPlayer.currentWindowIndex].name)
MyLog.e(TAG, "====== " + mSongList[mPlayer.currentWindowIndex].name)
MyLog.e(TAG, "======111 " + mSongList[mPlayer.currentWindowIndex].descripation)
}
else -> Log.e(TAG, "PlaybackStatus.IDLE")
}
}
}
}
private fun startService() {
val myService = Intent(this, MusicPlayerService ::class.java)
Util.startForegroundService(this,myService)
}
private fun stopService() {
val myService = Intent(this, MusicPlayerService ::class.java)
stopService(myService)
}
private fun setPlayPause(play: Boolean) {
if(mBound) {
if(mMusicPlayerService.isPlaying()) {
mMusicPlayerService.pause()
media_button.setImageResource(R.drawable.ic_play_arrow_black_24dp)
} else {
startService()
playerNotificationManager = mMusicPlayerService.getNotificationInstance()!!
/* if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent)
} else {
startService(intent)
}*/
mMusicPlayerService.play()
setProgress()
media_button.setImageResource(R.drawable.ic_pause_black_24dp)
}
}
/*isPlaying = play
exoPlayer.setPlayWhenReady(play)
if (!isPlaying) {
media_button.setImageResource(R.drawable.ic_play_arrow_black_24dp)
} else {
setProgress()
media_button.setImageResource(R.drawable.ic_pause_black_24dp)
}*/
}
private fun stringForTime(timeMs: Int): String {
val mFormatBuilder: StringBuilder
val mFormatter: Formatter
mFormatBuilder = StringBuilder()
mFormatter = Formatter(mFormatBuilder, Locale.getDefault())
val totalSeconds = timeMs / 1000
val seconds = totalSeconds % 60
val minutes = totalSeconds / 60 % 60
val hours = totalSeconds / 3600
mFormatBuilder.setLength(0)
return if (hours > 0) {
mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString()
} else {
mFormatter.format("%02d:%02d", minutes, seconds).toString()
}
}
private fun setProgress() {
seekPlayerProgress.progress = 0
seekPlayerProgress.max = mPlayer.getDuration().toInt() / 1000
position.setText(stringForTime(mPlayer.getCurrentPosition().toInt()))
duration.setText(stringForTime(mPlayer.getDuration().toInt()))
if (handler == null) handler = Handler()
//Make sure you update Seekbar on UI thread
handler!!.post(object : Runnable {
override fun run() {
if (mPlayer != null && ::mPlayer.isInitialized && isPlaying) {
seekPlayerProgress.max = mPlayer.getDuration().toInt() / 1000
val mCurrentPosition = mPlayer.getCurrentPosition().toInt() / 1000
seekPlayerProgress.progress = mCurrentPosition
position.setText(stringForTime(mPlayer.getCurrentPosition().toInt()))
duration.setText(stringForTime(mPlayer.getDuration().toInt()))
handler!!.postDelayed(this, 1000)
}
}
})
}
private fun initSeekBar() {
seekPlayerProgress.requestFocus()
seekPlayerProgress.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (!fromUser) {
// We're not interested in programmatically generated changes to
// the progress bar's position.
return
}
mPlayer.seekTo((progress * 1000).toLong())
}
override fun onStartTrackingTouch(seekBar: SeekBar) {
}
override fun onStopTrackingTouch(seekBar: SeekBar) {
}
})
seekPlayerProgress.setMax(0)
seekPlayerProgress.setMax(mPlayer.getDuration().toInt() / 1000)
}
private var doubleBackToExitPressedOnce = false
override fun onBackPressed() {
val fragment = getSupportFragmentManager().findFragmentByTag(Constants.SUB_CATEGORY_TAG)
if (fragment != null) {
getSupportFragmentManager().beginTransaction().remove(fragment).commit()
return
}
val fragment2 = getSupportFragmentManager().findFragmentByTag(Constants.DOWNLOAD_TAG)
if (fragment2 != null) {
getSupportFragmentManager().beginTransaction().remove(fragment2).commit()
return
}
if (doubleBackToExitPressedOnce) {
super.onBackPressed()
return
}
this.doubleBackToExitPressedOnce = true
this.showSnackBarToast("Please click BACK again to exit")
Handler().postDelayed(Runnable { doubleBackToExitPressedOnce = false }, 2000)
}
override fun onDestroy() {
if(::playerNotificationManager.isInitialized) {
playerNotificationManager.setPlayer(null)
}
killPlayer()
// stopService()
MyLog.e(TAG," #### onDestroy #### ")
super.onDestroy()
}
override fun onStop() {
super.onStop()
if(mBound) {
unbindService(mServiceConnection)
mBound = false
}
LocalBroadcastManager.getInstance(this)
.unregisterReceiver(broadCastReceiver)
MyLog.e(TAG," #### onStop #### ")
}
private fun killPlayer() {
if (mPlayer != null) {
mPlayer.release()
mediaSource = null
}
MyLog.e(TAG,"#### killPlayer ####")
}
private fun getHtmlText(str:String):String {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return (Html.fromHtml(str, Html.FROM_HTML_MODE_COMPACT)).toString()
} else {
return (Html.fromHtml(str)).toString()
}
}
private fun setMediaSession() {
mediaSession = MediaSessionCompat(this#MainActivity,"MEDIA_SESSION_TAG")
mediaSession.isActive = true
playerNotificationManager.setMediaSessionToken(mediaSession.sessionToken) //enhance media stye notification and provide artwork in lock screen
mediaSessionConnector = MediaSessionConnector(mediaSession)
//timeline is the internal representation of the pllaylist after the player has been prepared
mediaSessionConnector.setQueueNavigator(object : TimelineQueueNavigator(mediaSession){
override fun getMediaDescription(player: Player?, windowIndex: Int): MediaDescriptionCompat {
return getMediaDescriptionData(this#MainActivity, mSongList[windowIndex])
}
})
mediaSessionConnector.setPlayer(exoPlayer/*, null*/) //sync player wth media session
}
fun getMediaDescriptionData(context: Context, sample: RecentSong): MediaDescriptionCompat {
val extras = Bundle()
val options = BitmapFactory.Options()
options.inSampleSize = 8
// val bitmap = BitmapFactory.decodeFile(getBitmap(context, sample.bitmapResource), options)
val bitmap = context.getBitmap(R.drawable.bg)
extras.putParcelable(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, bitmap)
extras.putParcelable(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, bitmap)
return MediaDescriptionCompat.Builder()
.setMediaId(sample.song)
.setIconBitmap(bitmap)
.setTitle(sample.name)
.setDescription(sample.descripation)
.setExtras(extras)
.build()
}
override fun onStart() {
super.onStart()
bindService(Intent(this,MusicPlayerService::class.java),mServiceConnection,Context.BIND_AUTO_CREATE)
LocalBroadcastManager.getInstance(this)
.registerReceiver(broadCastReceiver, IntentFilter(MusicPlayerService().MUSIC_COMPLETED))
}
val broadCastReceiver = object : BroadcastReceiver() {
override fun onReceive(contxt: Context?, intent: Intent?) {
MyLog.e(TAG,"onReceive: ${intent?.getBooleanExtra(MESSAGE_KEY,false)}")
if(intent!!.getBooleanExtra(MESSAGE_KEY,false)) {
setPlayPause(intent.getBooleanExtra(MESSAGE_KEY,false))
if (intent.getBooleanExtra(MESSAGE_KEY,false)) {
Log.e(TAG, "PlaybackStatus.PLAYING")
} else {
Log.e(TAG, "PlaybackStatus.PAUSED")
}
titleTxt.setHtmlText(mSongList[mPlayer.currentWindowIndex].name)
MyLog.e(TAG,"====== "+mSongList[mPlayer.currentWindowIndex].name)
MyLog.e(TAG,"======111 "+mSongList[mPlayer.currentWindowIndex].descripation)
}
}
}
}
MusicPlayerService
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Binder
import android.os.IBinder
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.brahmakumaris.MainActivity
import com.brahmakumaris.R
import com.brahmakumaris.model.getDashboard.RecentSong
import com.brahmakumaris.util.MyLog
import com.brahmakumaris.util.getBitmap
import com.brahmakumaris.util.getPlaintextfromHtmlHtmlText
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.SimpleExoPlayer
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory
import com.google.android.exoplayer2.source.ConcatenatingMediaSource
import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.android.exoplayer2.ui.PlayerNotificationManager
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory
import com.google.android.exoplayer2.util.Util
class MusicPlayerService : Service() {
private val TAG = javaClass.simpleName
private val mContext: Context = this
private val mBinder = MyServiceBinder()
val MUSIC_COMPLETED = "music completed"
val mPlayer: SimpleExoPlayer by lazy { SimpleExoPlayer.Builder(this).build() }
private lateinit var playerNotificationManager: PlayerNotificationManager
var mSongList: ArrayList<RecentSong> = ArrayList()
override fun onCreate() {
super.onCreate()
MyLog.d(TAG,"onCreate: ")
}
fun playSong(mSongList: ArrayList<RecentSong> = ArrayList(), position: Int = 0)/* mContext: Context = this*/ {
this.mSongList = mSongList
val dataSourceFactory = DefaultDataSourceFactory(mContext, Util.getUserAgent(mContext,getString(R.string.app_name)))
val concateMediaSource = ConcatenatingMediaSource()
for (i in mSongList) {
val mediaSource = ProgressiveMediaSource
.Factory(
DefaultDataSourceFactory(mContext, dataSourceFactory),
DefaultExtractorsFactory()
)
.createMediaSource(/*i.uri*/Uri.parse(i.musicFile))
concateMediaSource.addMediaSource(mediaSource)
}
mPlayer.prepare(concateMediaSource)
mPlayer.seekToDefaultPosition(position)
mPlayer.playWhenReady =true
setNotification()
}
private fun setNotification() {
playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(
this,"channel_id",R.string.channelName,R.string.channelDescription,11,
object : PlayerNotificationManager.MediaDescriptionAdapter{
override fun createCurrentContentIntent(player: Player): PendingIntent? {
val intent = Intent(mContext, MainActivity::class.java)
return PendingIntent.getActivity(mContext,0,intent, PendingIntent.FLAG_UPDATE_CURRENT)
}
override fun getCurrentContentText(player: Player): String? {
// return mSongList[player.currentWindowIndex].descripation // descrption
return mSongList[player.currentWindowIndex].descripation.getPlaintextfromHtmlHtmlText() // descrption
}
override fun getCurrentContentTitle(player: Player): String {
// return mSongList[player.currentWindowIndex].name // title
return mSongList[player.currentWindowIndex].name.getPlaintextfromHtmlHtmlText()
}
override fun getCurrentLargeIcon(
player: Player,
callback: PlayerNotificationManager.BitmapCallback
): Bitmap? {
return mContext.getBitmap(R.drawable.bg)
}
} ,object : PlayerNotificationManager.NotificationListener {
override fun onNotificationStarted(notificationId: Int, notification: Notification) {
startForeground(notificationId,notification)
}
override fun onNotificationPosted(notificationId: Int, notification: Notification, ongoing: Boolean) {
startForeground(notificationId,notification)
}
override fun onNotificationCancelled(notificationId: Int, dismissedByUser: Boolean) {
stopSelf()
}
override fun onNotificationCancelled(notificationId: Int) {
stopSelf()
}
}
)
//show hide button
playerNotificationManager.setUseStopAction(false) //stop song
playerNotificationManager.setRewindIncrementMs(0) //hide rewind button
playerNotificationManager.setFastForwardIncrementMs(0) //hide fast forward button
playerNotificationManager.setPlayer(mPlayer)
}
inner class MyServiceBinder : Binder() {
fun getService(): MusicPlayerService = this#MusicPlayerService
}
fun getPlayerInstance(): SimpleExoPlayer? {
return mPlayer
}
fun getNotificationInstance(): PlayerNotificationManager? {
return playerNotificationManager
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
MyLog.d(TAG,"onStartCommand: ")
return START_NOT_STICKY
}
override fun onBind(intent: Intent?): IBinder? {
MyLog.d(TAG,"onBind: ")
return mBinder
}
override fun onUnbind(intent: Intent?): Boolean {
MyLog.d(TAG,"onUnbind: ")
return true
}
override fun onRebind(intent: Intent?) {
MyLog.d(TAG,"onRebind: ")
super.onRebind(intent)
}
override fun onDestroy() {
MyLog.e(TAG,"onDestroy: ")
super.onDestroy()
mPlayer.release()
if(::playerNotificationManager.isInitialized) {
playerNotificationManager.setPlayer(null)
}
}
// public client method
fun isPlaying():Boolean {
return mPlayer.isPlaying
}
fun play() {
mPlayer.setPlayWhenReady(true)
}
fun pause() {
mPlayer.setPlayWhenReady(false)
}
}
if I minimize the app then it's printing unbind and then it directly going to onDestroy why it's happening can anyone help me,
Any Help Would Be Highly Appreciated.
I just remove onStop unbind and unregister code and set in onDestroy and it's working for me.
You should be using startForeground() with a notification, to Android service that never stops running.
startForeground(1, notification)
Please have a look at this.
https://robertohuertas.com/2019/06/29/android_foreground_services/
I hope this will help you.
I experienced this issue because I set the player on notification manager to null on onStop which causes onNotificationCancelled to be called, you should check, if the notification was canceled by the user then you should stop the service, else leave it running.
override fun onNotificationCancelled(notificationId: Int, dismissedByUser:Boolean)
{
if (dismissedByUser) {
stopSelf()
}
}
Google Map GroundOverlay doesn't display the overlay image.
There was no error or any sort. (At least not in the log.)
The code was copied from Google IO 2019 app.
MapFragment File
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
mapView.getMapAsync {
it.mapType = GoogleMap.MAP_TYPE_NORMAL
it.addGroundOverlay(
GroundOverlayOptions()
.image(BitmapDescriptorFactory.fromResource(R.drawable.kirirom_map_overlay))
.positionFromBounds(viewModel.resortLocationBounds)
)
}
}
MapViewModel File
val resortLocationBounds: LatLngBounds = LatLngBounds(BuildConfig.MAP_VIEWPORT_BOUND_SW, BuildConfig.MAP_VIEWPORT_BOUND_NE)
References:
Full MapFragment file:
package kh.edu.kit.chain.app.vkclub.features.maps
import android.Manifest
import android.app.Dialog
import android.content.pm.PackageManager
import android.os.Bundle
import android.text.Html
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.addCallback
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.view.updatePaddingRelative
import androidx.core.widget.NestedScrollView
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.MapView
import com.google.android.gms.maps.model.BitmapDescriptorFactory
import com.google.android.gms.maps.model.GroundOverlayOptions
import com.google.android.gms.maps.model.LatLng
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.maps.android.data.geojson.GeoJsonLayer
import kh.edu.kit.chain.app.vkclub.R
import kh.edu.kit.chain.app.vkclub.databinding.MapsFragmentBinding
import kh.edu.kit.chain.app.vkclub.functions.doOnApplyWindowInsets
import kh.edu.kit.chain.app.vkclub.shared.BottomSheetBehavior
import kh.edu.kit.chain.app.vkclub.utils.getDrawableResourceForIcon
import org.koin.android.viewmodel.ext.android.viewModel
class MapsFragment : Fragment() {
companion object {
const val MIN_TIME: Long = 400
const val MIN_DISTANCE: Float = 1000F
private const val MAPVIEW_BUNDLE_KEY = "MapViewBundleKey"
private const val REQUEST_LOCATION_PERMISSION = 1
private const val FRAGMENT_MY_LOCATION_RATIONALE = "my_location_rationale"
// Threshold for when the marker description reaches maximum alpha. Should be a value
// between 0 and 1, inclusive, coinciding with a point between the bottom sheet's
// collapsed (0) and expanded (1) states.
private const val ALPHA_TRANSITION_END = 0.5f
// Threshold for when the marker description reaches minimum alpha. Should be a value
// between 0 and 1, inclusive, coinciding with a point between the bottom sheet's
// collapsed (0) and expanded (1) states.
private const val ALPHA_TRANSITION_START = 0.1f
}
private val viewModel: MapsViewModel by viewModel()
private var mapViewBundle: Bundle? = null
private lateinit var mapView: MapView
private lateinit var binding: MapsFragmentBinding
private lateinit var bottomSheetBehavior: BottomSheetBehavior<*>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (savedInstanceState != null) {
mapViewBundle = savedInstanceState.getBundle(MAPVIEW_BUNDLE_KEY)
}
requireActivity().onBackPressedDispatcher.addCallback(this) {
onBackPressed()
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = MapsFragmentBinding.inflate(inflater, container, false).apply {
lifecycleOwner = viewLifecycleOwner
viewModel = this#MapsFragment.viewModel
}
mapView = binding.map.apply {
onCreate(mapViewBundle)
}
if (savedInstanceState == null) {
viewModel.setMapVariant(MapVariant.DAY)
}
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
bottomSheetBehavior = BottomSheetBehavior.from(binding.bottomSheet)
val bottomSheetCallback = object : BottomSheetBehavior.BottomSheetCallback {
override fun onStateChanged(bottomSheet: View, newState: Int) {
val rotation = when (newState) {
BottomSheetBehavior.STATE_EXPANDED -> 0f
BottomSheetBehavior.STATE_COLLAPSED -> 180f
BottomSheetBehavior.STATE_HIDDEN -> 180f
else -> return
}
binding.expandIcon.animate().rotationX(rotation).start()
}
override fun onSlide(bottomSheet: View, slideOffset: Float) {
}
}
bottomSheetBehavior.addBottomSheetCallback(bottomSheetCallback)
binding.bottomSheet.post {
val state = bottomSheetBehavior.state
val slideOffset = when (state) {
BottomSheetBehavior.STATE_EXPANDED -> 1f
BottomSheetBehavior.STATE_COLLAPSED -> 0f
else -> -1f // BottomSheetBehavior.STATE_HIDDEN
}
bottomSheetCallback.onStateChanged(binding.bottomSheet, state)
bottomSheetCallback.onSlide(binding.bottomSheet, slideOffset)
}
val originalPeekHeight = bottomSheetBehavior.peekHeight
binding.root.doOnApplyWindowInsets { _, insets, _ ->
binding.map.getMapAsync {
it.setPadding(0, 0, 0, insets.systemWindowInsetBottom)
}
binding.descriptionScrollview.updatePaddingRelative(bottom = insets.systemWindowInsetBottom)
val gestureInsets = insets.systemGestureInsets
bottomSheetBehavior.peekHeight = gestureInsets.bottom + originalPeekHeight
}
binding.clickable.setOnClickListener {
if (bottomSheetBehavior.state == BottomSheetBehavior.STATE_COLLAPSED) {
bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
} else {
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
}
}
binding.descriptionScrollview.setOnScrollChangeListener { v: NestedScrollView, scrollX: Int, scrollY: Int, oldScrollX: Int, oldScrollY: Int ->
binding.sheetHeaderShadow.isActivated = v.canScrollVertically(-1)
}
mapView.getMapAsync { googleMap ->
googleMap.apply {
setOnMapClickListener { viewModel.dismissFeatureDetails() }
setOnCameraMoveListener {
viewModel.onZoomChanged(googleMap.cameraPosition.zoom)
}
enableMyLocation(false)
}
}
viewModel.mapVariant.observe(viewLifecycleOwner, Observer {
mapView.getMapAsync { googleMap ->
googleMap.clear()
viewModel.loadMapFeatures(googleMap)
}
})
viewModel.geoJsonLayer.observe(viewLifecycleOwner, Observer {
updateMarkers(it ?: return#Observer)
})
viewModel.selectedMarkerInfo.observe(viewLifecycleOwner, Observer {
updateInfoSheet(it ?: return#Observer)
})
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
mapView.getMapAsync {
it.mapType = GoogleMap.MAP_TYPE_NORMAL
it.addGroundOverlay(
GroundOverlayOptions()
.image(BitmapDescriptorFactory.fromResource(R.drawable.kirirom_map_overlay))
.positionFromBounds(viewModel.resortLocationBounds)
)
}
}
private fun updateInfoSheet(markerInfo: MarkerInfo) {
val iconRes = getDrawableResourceForIcon(binding.markerIcon.context, markerInfo.iconName)
binding.markerIcon.apply {
setImageResource(iconRes)
visibility = if (iconRes == 0) View.GONE else View.VISIBLE
}
binding.markerTitle.text = markerInfo.title
binding.markerSubtitle.apply {
text = markerInfo.subtitle
isVisible = !markerInfo.subtitle.isNullOrEmpty()
}
val description = Html.fromHtml(markerInfo.description ?: "")
val hasDescription = description.isNotEmpty()
binding.markerDescription.apply {
text = description
isVisible = hasDescription
}
binding.expandIcon.isVisible = hasDescription
binding.clickable.isVisible = hasDescription
}
private fun onBackPressed(): Boolean {
if (::bottomSheetBehavior.isInitialized &&
bottomSheetBehavior.state == BottomSheetBehavior.STATE_EXPANDED
) {
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
return true
}
return false
}
private fun updateMarkers(geoJsonLayer: GeoJsonLayer) {
geoJsonLayer.addLayerToMap()
geoJsonLayer.setOnFeatureClickListener { feature ->
viewModel.requestHighlightFeature(feature.id.split(",")[0])
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
val mapViewBundle = outState.getBundle(MAPVIEW_BUNDLE_KEY)
?: Bundle().apply { putBundle(MAPVIEW_BUNDLE_KEY, this) }
mapView.onSaveInstanceState(mapViewBundle)
}
override fun onDestroy() {
super.onDestroy()
mapView.onDestroy()
viewModel.onMapDestroyed()
}
override fun onStart() {
super.onStart()
mapView.onStart()
}
override fun onLowMemory() {
super.onLowMemory()
mapView.onLowMemory()
}
override fun onResume() {
super.onResume()
mapView.onResume()
}
override fun onPause() {
super.onPause()
mapView.onPause()
}
override fun onStop() {
super.onStop()
mapView.onStop()
}
private fun requestLocationPermission() {
val context = context ?: return
if (ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED
) {
return
}
if (shouldShowRequestPermissionRationale(Manifest.permission.ACCESS_FINE_LOCATION)) {
MyLocationRationaleFragment()
.show(childFragmentManager, FRAGMENT_MY_LOCATION_RATIONALE)
return
}
requestPermissions(
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_LOCATION_PERMISSION
)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_LOCATION_PERMISSION) {
if (grantResults.size == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
enableMyLocation()
} else {
MyLocationRationaleFragment()
.show(childFragmentManager, FRAGMENT_MY_LOCATION_RATIONALE)
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
}
private fun enableMyLocation(requestPermission: Boolean = false) {
val context = context ?: return
when {
ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED -> {
mapView.getMapAsync {
it.isMyLocationEnabled = true
}
viewModel.optIntoMyLocation()
}
requestPermission -> requestLocationPermission()
else -> viewModel.optIntoMyLocation(false)
}
}
class MyLocationRationaleFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return MaterialAlertDialogBuilder(context)
.setMessage(R.string.my_location_rationale)
.setPositiveButton(android.R.string.ok) { _, _ ->
parentFragment!!.requestPermissions(
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
REQUEST_LOCATION_PERMISSION
)
}
.setNegativeButton(android.R.string.cancel, null) // Give up
.create()
}
}
}
Full MapViewModel file:
package kh.edu.kit.chain.app.vkclub.features.maps
import androidx.lifecycle.*
import com.google.android.gms.maps.CameraUpdate
import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.model.LatLngBounds
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.maps.android.data.geojson.GeoJsonFeature
import com.google.maps.android.data.geojson.GeoJsonLayer
import com.google.maps.android.data.geojson.GeoJsonPoint
import kh.edu.kit.chain.app.vkclub.BuildConfig
import kh.edu.kit.chain.app.vkclub.R
import kh.edu.kit.chain.app.vkclub.domain.map.GeoJsonData
import kh.edu.kit.chain.app.vkclub.domain.map.LoadGeoJsonFeaturesUseCase
import kh.edu.kit.chain.app.vkclub.domain.map.LoadGeoJsonParams
import kh.edu.kit.chain.app.vkclub.functions.Event
import kh.edu.kit.chain.app.vkclub.functions.Result
class MapsViewModel(
private val loadGeoJsonFeaturesUseCase: LoadGeoJsonFeaturesUseCase
) : ViewModel() {
val resortLocationBounds: LatLngBounds = LatLngBounds(BuildConfig.MAP_VIEWPORT_BOUND_SW, BuildConfig.MAP_VIEWPORT_BOUND_NE)
val groundOverlayData = Pair(resortLocationBounds, R.drawable.kirirom_map_overlay)
private val _mapVariant = MutableLiveData<MapVariant>()
val mapVariant = Transformations.distinctUntilChanged(_mapVariant)
private val _mapCenterEvent = MutableLiveData<Event<CameraUpdate>>()
val mapCenterEvent: LiveData<Event<CameraUpdate>>
get() = _mapCenterEvent
private val loadGeoJsonResult = MutableLiveData<Result<GeoJsonData>>()
private val _geoJsonLayer = MediatorLiveData<GeoJsonLayer>()
val geoJsonLayer: LiveData<GeoJsonLayer>
get() = _geoJsonLayer
private val featureLookup: MutableMap<String, GeoJsonFeature> = mutableMapOf()
private var hasLoadedFeature = false
private var requestedFeatureId: String? = null
private val focusZoomLevel = BuildConfig.MAP_CAMERA_FOCUS_ZOOM
private var currentZoomLevel = 16
private val _bottomSheetStateEvent = MediatorLiveData<Event<Int>>()
val bottomSheetStateEvent: LiveData<Event<Int>>
get() = _bottomSheetStateEvent
private val _selectedMarkerInfo = MutableLiveData<MarkerInfo>()
val selectedMarkerInfo: LiveData<MarkerInfo>
get() = _selectedMarkerInfo
init {
_geoJsonLayer.addSource(loadGeoJsonResult) { result ->
if (result is Result.Success) {
hasLoadedFeature = true
setMapFeatures(result.data.featureMap)
_geoJsonLayer.value = result.data.geoJsonLayer
}
}
_bottomSheetStateEvent.addSource(mapVariant) {
dismissFeatureDetails()
}
}
fun setMapVariant(variant: MapVariant) {
_mapVariant.value = variant
}
fun onMapDestroyed() {
hasLoadedFeature = false
featureLookup.clear()
_geoJsonLayer.value = null
}
fun loadMapFeatures(googleMap: GoogleMap) {
val variant = _mapVariant.value ?: return
loadGeoJsonFeaturesUseCase(
LoadGeoJsonParams(googleMap, variant.markersResId),
loadGeoJsonResult
)
}
private fun setMapFeatures(features: Map<String, GeoJsonFeature>) {
featureLookup.clear()
featureLookup.putAll(features)
updateFeaturesVisibility(currentZoomLevel.toFloat())
val featureId = requestedFeatureId ?: return
requestedFeatureId = null
highlightFeature(featureId)
}
fun onZoomChanged(zoom: Float) {
val zoomInt = zoom.toInt()
if (currentZoomLevel != zoomInt) {
currentZoomLevel = zoomInt
updateFeaturesVisibility(zoom)
}
}
private fun updateFeaturesVisibility(zoom: Float) {
val selectedId = selectedMarkerInfo.value?.id
featureLookup.values.forEach { feature ->
if (feature.id != selectedId) {
val minZoom = feature.getProperty("minZoom")?.toFloatOrNull() ?: 0f
feature.pointStyle.isVisible = zoom >= minZoom
}
}
}
fun requestHighlightFeature(featureId: String) {
if (hasLoadedFeature) {
highlightFeature(featureId)
} else {
requestedFeatureId = featureId
}
}
private fun highlightFeature(featureId: String) {
val feature = featureLookup[featureId] ?: return
val geometry = feature.geometry as? GeoJsonPoint ?: return
val update = CameraUpdateFactory.newLatLngZoom(geometry.coordinates, focusZoomLevel)
_mapCenterEvent.value = Event(update)
val title = feature.getProperty("title")
_selectedMarkerInfo.value = MarkerInfo(
featureId,
title,
feature.getProperty("subtitle"),
feature.getProperty("description"),
feature.getProperty("icon")
)
_bottomSheetStateEvent.value = Event(BottomSheetBehavior.STATE_COLLAPSED)
}
fun dismissFeatureDetails() {
_bottomSheetStateEvent.value = Event(BottomSheetBehavior.STATE_HIDDEN)
_selectedMarkerInfo.value = null
}
fun optIntoMyLocation(optIn: Boolean = true) {
}
}
data class MarkerInfo(
val id: String,
val title: String,
val subtitle: String?,
val description: String?,
val iconName: String?
)
It turned out that the overlay was added successfully but it was clear away after the variant changed.
I would like to create and store new users also update the existing users in the following code
What code needs to be added to strore new users in a SQLitedatabase
I have a sqllitehelper class
which has adduser,updateuser,and readuser methods
From what i understand ,the signinfragment activity stores the instance of the logged in user and
if the instance exists then its loaded.When we logout we basically have to make a new user and we login
i would like to create,store,update users using an SQLite database.
package com.google.samples.apps.topeka.fragment
import android.annotation.TargetApi
import android.content.ContentValues
import android.content.Context
import android.content.Intent
import android.database.sqlite.SQLiteDatabase
import android.os.Build
import android.os.Bundle
import android.support.design.widget.FloatingActionButton
import android.support.v4.app.ActivityOptionsCompat
import android.support.v4.app.Fragment
import android.support.v4.util.Pair
import android.support.v4.view.ViewCompat
import android.support.v4.view.animation.FastOutSlowInInterpolator
import android.text.Editable
import android.text.TextWatcher
import android.transition.Transition
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.EditText
import android.widget.GridView
import android.widget.Toast
import android.widget.Toast.LENGTH_LONG
import com.google.samples.apps.topeka.activity.SignInActivity
import com.google.samples.apps.topeka.adapter.AvatarAdapter
import com.google.samples.apps.topeka.base.R
import com.google.samples.apps.topeka.helper.ActivityLaunchHelper
import com.google.samples.apps.topeka.helper.ApiLevelHelper
import com.google.samples.apps.topeka.helper.DefaultLogin
import com.google.samples.apps.topeka.helper.TAG
import com.google.samples.apps.topeka.helper.TransitionHelper
import com.google.samples.apps.topeka.helper.isLoggedIn
import com.google.samples.apps.topeka.helper.login
import com.google.samples.apps.topeka.helper.onLayoutChange
import com.google.samples.apps.topeka.helper.onSmartLockResult
import com.google.samples.apps.topeka.model.Avatar
import com.google.samples.apps.topeka.model.Player
import com.google.samples.apps.topeka.persistence.PPlayer
import com.google.samples.apps.topeka.widget.TextWatcherAdapter
import com.google.samples.apps.topeka.widget.TransitionListenerAdapter
import com.google.samples.apps.topeka.persistence.TopekaDatabaseHelper;
/**
* Enable selection of an [Avatar] and user name.
*/
class SignInFragment : Fragment() {
private var firstNameView: EditText? = null
private var lastInitialView: EditText? = null
private var doneFab: FloatingActionButton? = null
private var avatarGrid: GridView? = null
private var firstName = ""
private var lastInitial= ""
private var avatar = ""
private val edit by lazy { arguments?.getBoolean(ARG_EDIT, false) ?: false }
private var selectedAvatarView: View? = null
private var player: Player? = null
private var selectedAvatar: Avatar? = null
var a = getContext()
override fun onCreate(savedInstanceState: Bundle?) {
var a = context
val resources = context!!.resources
var ss = TopekaDatabaseHelper(requireActivity())
val newValues = ContentValues().apply {
// Sets the values of each column and inserts the value.
// The arguments to the "put"
// method are "column name" and "value"
}
if (savedInstanceState != null) {
val avatarIndex = savedInstanceState.getInt(KEY_SELECTED_AVATAR_INDEX)
if (avatarIndex != GridView.INVALID_POSITION) {
selectedAvatar = Avatar.values()[avatarIndex]
}
}
activity?.run {
if (isLoggedIn()) {
navigateToCategoryActivity()
Toast.makeText(requireContext(),"old", LENGTH_LONG)
} else {
maketext("new player")
login.loginPlayer(this, ::onSuccessfulLogin)
}
}
super.onCreate(savedInstanceState)
}
/**
* Called when logged in successfully.
*/
private fun onSuccessfulLogin(player: Player) {
if (login != DefaultLogin) return
this.player = player
if (edit) {
with(player) {
firstNameView?.setText(player.firstName)
lastInitialView?.run {
setText(player.lastInitial)
requestFocus()
setSelection(length())
var db = TopekaDatabaseHelper(context)
db.adduser(player.firstName,player.lastInitial)
maketext("saved new")
}
this#SignInFragment.player = player.also {
if (activity != null)
login.savePlayer(activity!!, this, { selectAvatar(it.avatar!!) })
maketext("saved new")
}
}
} else {
Toast.makeText(requireContext(),"new", LENGTH_LONG)
navigateToCategoryActivity()
}
}
private fun maketext(ss:String){
Toast.makeText(requireContext(),ss,LENGTH_LONG)
}
private fun navigateToCategoryActivity() {
activity?.run {
ActivityLaunchHelper.launchCategorySelection(this)
supportFinishAfterTransition()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
activity?.onSmartLockResult(
requestCode,
resultCode,
data,
success = {
player = it
initContents()
navigateToCategoryActivity()
},
failure = {
activity?.run {
login.loginPlayer(this, ::onSuccessfulLogin)
}
}
)
super.onActivityResult(requestCode, resultCode, data)
}
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?): View? {
val contentView = inflater.inflate(R.layout.fragment_sign_in, container, false)
contentView.onLayoutChange {
avatarGrid?.apply {
adapter = AvatarAdapter(activity!!)
onItemClickListener = AdapterView.OnItemClickListener { _, view, position, _ ->
selectedAvatarView = view
selectedAvatar = Avatar.values()[position]
// showing the floating action button if input data is valid
showFab()
}
numColumns = calculateSpanCount()
selectedAvatar?.run { selectAvatar(this) }
}
}
return contentView
}
/**
* Calculates spans for avatars dynamically.
* #return The recommended amount of columns.
*/
private fun calculateSpanCount(): Int {
val avatarSize = resources.getDimensionPixelSize(R.dimen.size_fab)
val avatarPadding = resources.getDimensionPixelSize(R.dimen.spacing_double)
return (avatarGrid?.width ?: 0) / (avatarSize + avatarPadding)
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putInt(KEY_SELECTED_AVATAR_INDEX, (avatarGrid?.checkedItemPosition ?: 0))
super.onSaveInstanceState(outState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
firstNameView = view.findViewById<EditText>(R.id.first_name)
lastInitialView = view.findViewById<EditText>(R.id.last_initial)
doneFab = view.findViewById<FloatingActionButton>(R.id.done)
avatarGrid = view.findViewById<GridView>(R.id.avatars)
if (edit || (player != null && player!!.valid())) {
initContentViews()
initContents()
}
hideEmptyView()
super.onViewCreated(view, savedInstanceState)
}
private fun hideEmptyView() {
view?.run {
findViewById<View>(R.id.empty).visibility = View.GONE
findViewById<View>(R.id.content).visibility = View.VISIBLE
}
}
private fun initContentViews() {
val textWatcher = object : TextWatcher by TextWatcherAdapter {
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
// hiding the floating action button if text is empty
if (s.isEmpty()) {
doneFab?.hide()
}
}
// showing the floating action button if avatar is selected and input data is valid
override fun afterTextChanged(s: Editable) {
if (isAvatarSelected() && isInputDataValid()) doneFab?.show()
}
}
firstNameView?.addTextChangedListener(textWatcher)
lastInitialView?.addTextChangedListener(textWatcher)
doneFab?.setOnClickListener {
if (it.id == R.id.done) {
val first = firstNameView?.text?.toString()
val last = lastInitialView?.text?.toString()
activity?.run {
val toSave = player?.apply {
// either update the existing player object
firstName = first
lastInitial = last
avatar = selectedAvatar
} ?: Player(first, last, selectedAvatar) /* or create a new one */
login.savePlayer(this, toSave) {
Toast.makeText(this,"done",LENGTH_LONG)
Log.d(TAG, "Saving login info successful.")
}
}
}
removeDoneFab {
performSignInWithTransition(selectedAvatarView
?: avatarGrid?.getChildAt(selectedAvatar!!.ordinal))
}
}
}
private fun removeDoneFab(endAction: () -> Unit) {
ViewCompat.animate(doneFab)
.scaleX(0f)
.scaleY(0f)
.setInterpolator(FastOutSlowInInterpolator())
.withEndAction(endAction)
.start()
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
private fun performSignInWithTransition(v: View? = null) {
if (v == null || ApiLevelHelper.isLowerThan(Build.VERSION_CODES.LOLLIPOP)) {
// Don't run a transition if the passed view is null
activity?.run {
navigateToCategoryActivity()
}
return
}
if (ApiLevelHelper.isAtLeast(Build.VERSION_CODES.LOLLIPOP)) {
activity?.run {
window.sharedElementExitTransition.addListener(object :
Transition.TransitionListener by TransitionListenerAdapter {
override fun onTransitionEnd(transition: Transition) {
finish()
}
})
val pairs = TransitionHelper.createSafeTransitionParticipants(this, true,
Pair(v, getString(R.string.transition_avatar)))
val options = ActivityOptionsCompat.makeSceneTransitionAnimation(this, *pairs)
ActivityLaunchHelper.launchCategorySelection(this, options)
}
}
}
private fun initContents() {
player?.run {
valid().let {
firstNameView?.setText(firstName)
lastInitialView?.setText(lastInitial)
avatar?.run { selectAvatar(this) }
}
}
}
private fun isAvatarSelected() = selectedAvatarView != null || selectedAvatar != null
private fun selectAvatar(avatar: Avatar) {
selectedAvatar = avatar
avatarGrid?.run {
requestFocusFromTouch()
setItemChecked(avatar.ordinal, true)
}
showFab()
}
private fun showFab() {
if (isInputDataValid()) doneFab?.show()
}
private fun isInputDataValid() =
firstNameView?.text?.isNotEmpty() == true &&
lastInitialView?.text?.isNotEmpty() == true &&
selectedAvatar != null
companion object {
private const val ARG_EDIT = "EDIT"
private const val KEY_SELECTED_AVATAR_INDEX = "selectedAvatarIndex"
fun newInstance(edit: Boolean = false): SignInFragment {
return SignInFragment().apply {
arguments = Bundle().apply {
putBoolean(ARG_EDIT, edit)
}
}
}
}
}