Im newbie Android(Kotlin), I want send data from my app to widget and show on home screen. Thank you so much. Sorry for bad english.
In MainActivity class, I am using Room to save it.
private fun saveNewCityCollection(informationWeather: InformationWeather, state: Boolean = Constants.OTHER_LOCATION) {
val weatherRepository = WeatherRepository(applicationContext)
val cityCollection = CityCollection()
cityCollection.cityName = informationWeather.location.name
cityCollection.countryName = informationWeather.location.country
cityCollection.temp = informationWeather.current.temp
cityCollection.appTemp = informationWeather.current.appTemp
cityCollection.humidity = informationWeather.current.humidity
cityCollection.wind = informationWeather.current.windSpeed
cityCollection.cloud = informationWeather.current.cloud
cityCollection.description = informationWeather.current.condition.description
cityCollection.icon = informationWeather.current.condition.icon
cityCollection.date = informationWeather.current.date
cityCollection.day = informationWeather.current.isDay
if (location == cityCollection.cityName) {
cityCollection.state = true
}
if (state) {
cityCollection.state = true
val editor = getSharedPreferences(getString(R.string.shared_preference_name), Context.MODE_PRIVATE).edit()
editor.putString(Constants.NAME_LOCATION, cityCollection.cityName)
editor.apply()
}
weatherRepository.insert(cityCollection, this)
}
And my model: CityColection, I am using Parcelable.
class CityCollection() : Parcelable {
#PrimaryKey
#NonNull
#ColumnInfo(name = "cityname")
lateinit var cityName: String
lateinit var countryName: String
var state = Constants.OTHER_LOCATION
var temp: Float = 0F
var appTemp: Float = 0F
var humidity: Int = 0
var wind: Float = 0F
var cloud: Int = 0
var day: Int = 0
lateinit var description: String
var icon: String = "na"
var date: String = "dd/mm/yy"
constructor(parcel: Parcel) : this() {
cityName = parcel.readString()
countryName = parcel.readString()
state = parcel.readByte() != 0.toByte()
temp = parcel.readFloat()
appTemp = parcel.readFloat()
humidity = parcel.readInt()
wind = parcel.readFloat()
cloud = parcel.readInt()
day = parcel.readInt()
description = parcel.readString()
icon = parcel.readString()
date = parcel.readString()
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(cityName)
parcel.writeString(countryName)
parcel.writeByte(if (state) 1 else 0)
parcel.writeFloat(temp)
parcel.writeFloat(appTemp)
parcel.writeInt(humidity)
parcel.writeFloat(wind)
parcel.writeInt(cloud)
parcel.writeInt(day)
parcel.writeString(description)
parcel.writeString(icon)
parcel.writeString(date)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<CityCollection> {
override fun createFromParcel(parcel: Parcel): CityCollection {
return CityCollection(parcel)
}
override fun newArray(size: Int): Array<CityCollection?> {
return arrayOfNulls(size)
}
}
}
In My Fragment, I am getParcelable to get data.
private fun initData() {
val bundle = arguments
if (bundle != null) {
val cityCollection: CityCollection = bundle.getParcelable(Constants.CITY_COLLECTION)
tvWind.text = cityCollection.wind.toInt().toString() + " km/h"
}
}
And my ViewPagerAdapter:
class ViewPagerAdapter(fm: FragmentManager, private var listCityCollection: MutableList<CityCollection>)
: FragmentStatePagerAdapter(fm) {
override fun getItem(position: Int): Fragment? {
return newFragment(listCityCollection[position])
}
override fun getItemPosition(`object`: Any): Int {
return PagerAdapter.POSITION_NONE
}
override fun getCount(): Int {
return listCityCollection.size
}
private fun newFragment(cityCollection: CityCollection): FragmentShowWeatherForecast {
val fragmentShowWeatherForecast = FragmentShowWeatherForecast()
val bundle = Bundle()
bundle.putParcelable(Constants.CITY_COLLECTION, cityCollection)
fragmentShowWeatherForecast.arguments = bundle
return fragmentShowWeatherForecast
}
}
My Widget class:
class WeatherWidget : AppWidgetProvider() {
override fun onEnabled(context: Context?) {
super.onEnabled(context)
val intent = Intent(context, WeatherWidget::class.java)
intent.action = Constants.UPDATE_WIDGET
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0)
val alarmManager = context?.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.setRepeating(AlarmManager.RTC, System.currentTimeMillis(), (Constants.UPDATE_INTERVAL * 1000).toLong(), pendingIntent)
}
#SuppressLint("SimpleDateFormat")
override fun onUpdate(context: Context?, appWidgetManager: AppWidgetManager?, appWidgetIds: IntArray?) {
if (appWidgetIds != null) {
for (i in appWidgetIds.indices) {
val views = RemoteViews(context?.packageName, R.layout.widget_weather)
val openApp = Intent(context, MainActivity::class.java)
val pIntent = PendingIntent.getActivity(context, 0, openApp, 0)
views.setOnClickPendingIntent(R.id.rlWidget, pIntent)
appWidgetManager?.updateAppWidget(appWidgetIds[i], views)
}
}
}
override fun onDeleted(context: Context?, appWidgetIds: IntArray?) {
super.onDeleted(context, appWidgetIds)
Toast.makeText(context, "onDeleted()", Toast.LENGTH_LONG).show()
}
override fun onDisabled(context: Context?) {
super.onDisabled(context)
val intent = Intent(context, WeatherWidget::class.java)
intent.action = Constants.UPDATE_WIDGET
val pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0)
val alarmManager = context?.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmManager.cancel(pendingIntent)
}
}
My solution is save your model as json in SharedPreferences, some thing like below
preferenceApi.put(PREFS_WEATHER_DATA + appWidgetId,
new GsonBuilder().create().toJson(data, WeatherData.class));
and get it in onUpdate() of Provider of Widget
SharedPreferenceApi preferenceApi = new SharedPreferenceApi(context);
Gson gson = new Gson();
String jsonData = preferenceApi.get(PREFS_WEATHER_DATA + widgetId, String.class);
WeatherData weatherData = gson.fromJson(jsonData, WeatherData.class);
hope this helps
Related
So i am trying 2 things here and it seems to be all right but i am not beeing able to reach my API response. First i want to Filter my API to All information, only Images and only Pdf.- and second to make a recyclerView OnItemClickListener. If you can help to provide me this error i will very graceful. Thank You .
//Main Activity
class MainActivity : AppCompatActivity() {
private lateinit var recyclerview_users: RecyclerView
private lateinit var imageList : ArrayList<ImageModel>
private lateinit var imageAdapter: ImageAdapter
private var imageModel: List<ImageModel> = mutableListOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val allButton = findViewById<Button>(R.id.button_all)
allButton.setOnClickListener {
getBlob()
}
val photoButton = findViewById<Button>(R.id.button_image)
photoButton.setOnClickListener {
filterImageModel("photo")
}
val pdfButton = findViewById<Button>(R.id.button_pdf)
pdfButton.setOnClickListener {
filterImageModel("pdf")
}
recyclerview_users = findViewById(R.id.recycler_view)
recyclerview_users.setHasFixedSize(true)
recyclerview_users.layoutManager = LinearLayoutManager(this)
imageList = ArrayList()
imageAdapter = ImageAdapter(imageModel)
recyclerview_users.adapter = imageAdapter
var layoutManager = GridLayoutManager(this, 3)
recyclerview_users.layoutManager = layoutManager
getBlob()
}
private fun getBlob(){
val plcApi = ServiceGenerator.buildService(PLCApi::class.java)
val call = plcApi.getBlob()
call.enqueue(object : Callback<MutableList<ImageModel>>{
override fun onResponse(
call: Call<MutableList<ImageModel>>,
response: Response<MutableList<ImageModel>>
){
if (response.isSuccessful){
imageModel = response.body()!!
imageAdapter.updateImageModel(imageModel)
val imageList = response.body()!! as ArrayList<ImageModel>
val imageAdapter = ImageAdapter(imageList)
recyclerview_users.adapter = imageAdapter
imageAdapter.setOnitemClickListener { imageModel ->
val intent = Intent(this#MainActivity, ImageInformation::class.java)
startActivity(intent)
}
}else{
Toast.makeText(this#MainActivity, "Failed to retrieve data, please try again", Toast.LENGTH_SHORT).show()
}
}
override fun onFailure(call: Call<MutableList<ImageModel>>, t: Throwable){
Toast.makeText(this#MainActivity, "Network error, please check your connection", Toast.LENGTH_SHORT).show()
}
})
}
private fun filterImageModel(contentType: String) {
val filteredImageModel = imageModel.filter {
it.contentType == contentType
}
imageModel = filteredImageModel
imageAdapter.updateImageModel(filteredImageModel)
}
}
//ImageAdapter
class ImageAdapter(private var imageModel: List<ImageModel>) : RecyclerView.Adapter<ImageAdapter.ImageViewHolder>() {
private var onItemClickListener: ((ImageModel) -> Unit)? = null
fun setOnitemClickListener(listener: (ImageModel) -> Unit){
onItemClickListener = listener
}
fun updateImageModel(newImageModel: List<ImageModel>){
imageModel = newImageModel
notifyDataSetChanged()
}
class ImageViewHolder(itemView: View): RecyclerView.ViewHolder(itemView){
var downloadUrl: TextView = itemView.findViewById(R.id.downloadUrl)
var previewUrl: ImageView = itemView.findViewById(R.id.previewUrl)
var id: TextView = itemView.findViewById(R.id.id)
var filename: TextView = itemView.findViewById(R.id.fileName)
var filesize: TextView = itemView.findViewById(R.id.filesize)
var contentType: TextView = itemView.findViewById(R.id.contentType)
var createdBy: TextView = itemView.findViewById(R.id.createdBy)
var createdTimestamp: TextView = itemView.findViewById(R.id.createdTimestamp)
var creationSource: TextView = itemView.findViewById(R.id.creationSource)
var domainIdentityType: TextView = itemView.findViewById(R.id.domainIdentityType)
var domainIdentityValue: TextView = itemView.findViewById(R.id.domainIdentityValue)
var tags: TextView = itemView.findViewById(R.id.tags)
var description: TextView = itemView.findViewById(R.id.description)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.layout_items, parent, false)
return ImageViewHolder(view)
}
#SuppressLint("CheckResult", "ResourceType")
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
val imageId = imageModel[position]
holder.downloadUrl.text = imageId.downloadUrl
if (imageId.contentType!!.startsWith("image")){
Glide.with(holder.itemView.context).load(imageId.previewUrl).into(holder.previewUrl)
}else{
holder.previewUrl.setImageResource(R.drawable.pdf)
}
holder.id.text = imageId.id
holder.filename.text = imageId.fileName
holder.filesize.text = imageId.fileName.toString()
holder.contentType.text = imageId.contentType
holder.createdBy.text = imageId.createdBy
holder.createdTimestamp.text = imageId.createdTimestamp
holder.creationSource.text = imageId.creationSource
holder.domainIdentityType.text = imageId.domainIdentityType
holder.domainIdentityValue.text = imageId.domainIdentityValue
holder.tags.text = imageId.tags.toString()
holder.description.text = imageId.description
holder.itemView.setOnClickListener{
onItemClickListener?.invoke(imageId)
}
}
override fun getItemCount(): Int {
return imageModel.size
}
}
//ImageModel
data class ImageModel(
#SerializedName("downloadUrl")
var downloadUrl: String? = null,
#SerializedName("previewUrl")
val previewUrl: String? = null,
#SerializedName("id")
var id: String? = null,
#SerializedName("fileName")
val fileName: String? = null,
#SerializedName("filesize")
val filesize: Int,
#SerializedName("contentType")
val contentType: String? = null,
#SerializedName("createdBy")
val createdBy: String? = null,
#SerializedName("createdTimestamp")
val createdTimestamp: String? = null,
#SerializedName("creationSource")
val creationSource: String? = null,
#SerializedName("domainIdentityType")
val domainIdentityType: String? = null,
#SerializedName("domainIdentityValue")
val domainIdentityValue: String? = null,
#SerializedName("tags")
val tags: List<String>? = null,
#SerializedName("description")
val description: String? = null)
: Parcelable{
constructor(parcel: Parcel) : this(
parcel.readString()!!,
parcel.readString()!!,
parcel.readString()!!,
parcel.readString()!!,
parcel.readInt(),
parcel.readString()!!,
parcel.readString()!!,
parcel.readString()!!,
parcel.readString()!!,
parcel.readString()!!,
parcel.readString()!!,
parcel.createStringArrayList(),
parcel.readString()!!
)
override fun describeContents(): Int {
return 0
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(downloadUrl)
parcel.writeString(previewUrl)
parcel.writeString(id)
parcel.writeString(fileName)
parcel.writeInt(filesize)
parcel.writeString(contentType)
parcel.writeString(createdBy)
parcel.writeString(createdTimestamp)
parcel.writeString(creationSource)
parcel.writeString(domainIdentityType)
parcel.writeString(domainIdentityValue)
parcel.writeString(tags.toString())
parcel.writeString(description)
}
companion object CREATOR : Parcelable.Creator<ImageModel> {
override fun createFromParcel(parcel: Parcel): ImageModel {
return ImageModel(parcel)
}
override fun newArray(size: Int): Array<ImageModel?> {
return arrayOfNulls(size)
}
}
}
//PLC API
interface PLCApi {
#GET("/BlobStorage/Blobs?StorageContainerId=ccef4ed7-7ec7-421d-8afb-3dce0a8be39c")
fun getBlob():Call<MutableList<ImageModel>>
}
//ServiceGenerator
object ServiceGenerator {
private val client = OkHttpClient.Builder().build()
private val retrofit = Retrofit.Builder()
.baseUrl("http://app-bbg-blob-assessment.azurewebsites.net/")
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
fun <T> buildService(service: Class<T>): T {
return retrofit.create(service)
}
}
//ImageInformation where i want to invoke the ClickListener
class ImageInformation : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_image_information)
val image = intent.getParcelableExtra<ImageModel>("ImageModel")
if (image != null){
val downloadUrl: TextView = findViewById(R.id.downloadUrla)
val previewUrl : ImageView = findViewById(R.id.previewUrla)
val id: TextView = findViewById(R.id.idsa)
val filename: TextView = findViewById(R.id.fileNamea)
val filesize: TextView = findViewById(R.id.filesizea)
val contentType: TextView = findViewById(R.id.contentTypea)
val createdBy: TextView = findViewById(R.id.createdBya)
val createdTimestamp: TextView = findViewById(R.id.createdTimestampa)
val creationSource: TextView = findViewById(R.id.creationSourcea)
val domainIdentityType: TextView = findViewById(R.id.domainIdentityTypea)
val domainIdentityValue: TextView = findViewById(R.id.domainIdentityValuea)
val tags: TextView = findViewById(R.id.tagsa)
val description: TextView = findViewById(R.id.descriptiona)
downloadUrl.text = image.downloadUrl
Glide.with(this).load(image.previewUrl).into(previewUrl)
id.text = image.id
filename.text = image.fileName
filesize.text = image.filesize.toString()
contentType.text = image.contentType
createdBy.text = image.createdBy
createdTimestamp.text = image.createdTimestamp
creationSource.text = image.creationSource
domainIdentityType.text = image.domainIdentityType
domainIdentityValue.text = image.domainIdentityValue
tags.text = image.tags.toString()
description.text = image.description
image.downloadUrl = downloadUrl.text as String?
image.id = id.text as String?
}
}
}
I have a music player but i noticed that android and other devices don't recognize what song is playing.
For example in mi band 4 i can control the music if i have it on youtube music but not in my app or even in my launcher the at a glance widget recognizes it's playing.
Example:
Now that you know the context, what do i need to make it work or what am i missing?
Here's my service that plays the music:
class SimpleMPService: Service() {
private val mBinder = LocalBinder()
private lateinit var notification: Notification
private lateinit var notificationManager: NotificationManager
var playList = ArrayList<Song>()
private var shuffledPlaylist = ArrayList<Song>()
var currentSongPosition: Int = 0
private var currentSongPath: String = ""
var onRepeatMode = false
private lateinit var audioManager: AudioManager
//Listeners
var onMusicSelectedListener: OnMusicSelectedListener? = null
var onMusicSelectedListenerToQueue: OnMusicSelectedListenerToQueue? = null //Since there is no way to have two listeners at same time it needs another listener to the queue list
var onMusicPausedListener: OnMusicPausedListener? = null
var onPlaylistAdded: OnPlaylistsAdded? = null
var onMusicResumedListener: OnMusicResumedListener? = null
var onMusicSecondPassedListener: OnSecondPassedListener? = null
var onMusicShuffleToggledListener: OnMusicShuffleToggledListener? = null
var onMediaPlayerStoppedListener: OnMediaPlayerStoppedListener? = null
//Player States
private var serviceStarted = false
var musicShuffled = false
private var musicStarted = false
//Others
private lateinit var mediaButtonReceiver: ComponentName
private lateinit var mediaSession: MediaSessionCompat
inner class LocalBinder : Binder() {
fun getService(): SimpleMPService = this#SimpleMPService
}
companion object {
private val mediaPlayer = MediaPlayer()
fun startService(context: Context) {
val startIntent = Intent(context, SimpleMPService::class.java)
context.startForegroundService(startIntent)
}
}
override fun onBind(intent: Intent?): IBinder {
val context = this
mediaButtonReceiver = ComponentName(context, ReceiverPlayPause::class.java)
mediaSession = MediaSessionCompat(context, "SessionTag")
mediaSession.setCallback(object : MediaSessionCompat.Callback(){
override fun onMediaButtonEvent(mediaButtonIntent: Intent): Boolean {
val ke = mediaButtonIntent.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT)
if( ke?.action == KeyEvent.ACTION_DOWN ){
if( ke.keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS )
previousSong( context )
if( ke.keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE )
pauseResumeMusic( context )
if( ke.keyCode == KeyEvent.KEYCODE_MEDIA_PLAY )
pauseResumeMusic( context )
if( ke.keyCode == KeyEvent.KEYCODE_MEDIA_NEXT )
skipSong( context )
}
return super.onMediaButtonEvent(mediaButtonIntent)
}
})
return mBinder
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return START_STICKY
}
override fun onCreate() {
super.onCreate()
notificationManager = this.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
}
fun getCurrentPlaylist(): ArrayList<Song>{
return if(!musicShuffled) playList else shuffledPlaylist
}
fun isMusicPlayingOrPaused(): Boolean{ return musicStarted }
fun toggleShuffle(){
if( !musicShuffled ){
musicShuffled = true
shuffledPlaylist = ArrayList()
val tempShuffledPlaylist = ArrayList<Song>()
//Adds the current song to first position
playList.forEach { song ->
if (song.path != currentSongPath)
tempShuffledPlaylist.add(song)
else
shuffledPlaylist.add( song )
}
//Shuffles the temp playlist and adds it to the one with just the current song
tempShuffledPlaylist.shuffle()
for( song in tempShuffledPlaylist )
shuffledPlaylist.add( song )
currentSongPosition = 0
}
else{
musicShuffled = false
for( i in playList.indices ){
if( playList[i].path == currentSongPath ){
currentSongPosition = i
break
}
}
}
onMusicShuffleToggledListener?.onMusicShuffleToggled(musicShuffled)
}
fun enableShuffle(){
musicShuffled = true
shuffledPlaylist = ArrayList(playList)
shuffledPlaylist.shuffle()
onMusicShuffleToggledListener?.onMusicShuffleToggled(true)
currentSongPosition = 0
}
fun setPlaylist( newPlaylist: ArrayList<Song> ){ playList = newPlaylist }
fun playSongAndEnableShuffle(context: Context, position: Int){
val selectedSong = playList[position]
shuffledPlaylist = ArrayList(playList)
shuffledPlaylist.shuffle()
shuffledPlaylist.removeIf{ it.path == selectedSong.path }
shuffledPlaylist.add(0, selectedSong )
currentSongPosition = 0
playSong(context)
musicShuffled = true
}
fun isMusicPlaying(): Boolean{
return mediaPlayer.isPlaying
}
fun getCurrentSongPath(): String{ return currentSongPath }
private val audioFocusChangeListener = OnAudioFocusChangeListener { focusChange ->
when (focusChange) {
AudioManager.AUDIOFOCUS_GAIN -> {}
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT->{}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {
if( mediaPlayer.isPlaying )
pauseMusic(this )
}
AudioManager.AUDIOFOCUS_LOSS -> {
if( mediaPlayer.isPlaying )
pauseMusic(this )
}
}
}
private val focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).run {
setAudioAttributes(AudioAttributes.Builder().run {
setUsage(AudioAttributes.USAGE_MEDIA)
setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
build()
})
setAcceptsDelayedFocusGain(true)
setOnAudioFocusChangeListener(audioFocusChangeListener)
build()
}
fun playSong(context: Context){
serviceStarted = true
musicStarted = true
val songPath: String
val songTitle: String
val songArtist: String
val songID: Long
val songAlbumID: Long
val songAlbumArt: Bitmap
val songDuration: Int
if( !musicShuffled ) {
songPath = playList[currentSongPosition].path
songTitle = playList[currentSongPosition].title
songArtist = playList[currentSongPosition].artistName
songID = playList[currentSongPosition].id
songAlbumID = playList[currentSongPosition].albumID
songAlbumArt = GetSongs.getSongAlbumArt(context, songID, songAlbumID)
songDuration = playList[currentSongPosition].duration
}
else{
songPath = shuffledPlaylist[currentSongPosition].path
songTitle = shuffledPlaylist[currentSongPosition].title
songArtist = shuffledPlaylist[currentSongPosition].artistName
songID = shuffledPlaylist[currentSongPosition].id
songAlbumID = shuffledPlaylist[currentSongPosition].albumID
songAlbumArt = GetSongs.getSongAlbumArt(context, songID, songAlbumID)
songDuration = shuffledPlaylist[currentSongPosition].duration
}
currentSongPath = songPath
val isAudioLimited = PreferenceManager.getDefaultSharedPreferences(context).getBoolean("setting_limitAudioVolume", true)
mediaPlayer.reset()
mediaPlayer.setDataSource(songPath)
when(isAudioLimited){
true-> mediaPlayer.setVolume(0.08F, 0.08F)
false-> mediaPlayer.setVolume(0.1F, 0.1F)
}
mediaPlayer.prepareAsync()
mediaPlayer.setOnPreparedListener {
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
requestPlayWithFocus()
mediaSession.isActive = true
//Open App
val openAppIntent = Intent( context, ActivityMain::class.java )
val pendingOpenAppIntent = TaskStackBuilder.create( context ).run{
addNextIntentWithParentStack(openAppIntent)
getPendingIntent( 0, PendingIntent.FLAG_IMMUTABLE )
}
//Stop Service
val stopIntent = Intent(context, ReceiverStop::class.java )
val pendingStopIntent = PendingIntent.getBroadcast( context, 1, stopIntent, PendingIntent.FLAG_IMMUTABLE )
//Previous Music
val previousSongIntent = Intent(context, ReceiverPreviousSong::class.java )
val pendingPreviousSongIntent = PendingIntent.getBroadcast( context, 1, previousSongIntent, PendingIntent.FLAG_IMMUTABLE )
//Pauses/Plays music
val playPauseIntent = Intent(context, ReceiverPlayPause::class.java )
val pendingPlayPauseIntent = PendingIntent.getBroadcast( context, 1, playPauseIntent, PendingIntent.FLAG_IMMUTABLE )
//Skips to next music
val skipSongIntent = Intent(context, ReceiverSkipSong::class.java )
val pendingSkipSongIntent = PendingIntent.getBroadcast( context, 1, skipSongIntent, PendingIntent.FLAG_IMMUTABLE )
notification = NotificationCompat.Builder(context, "Playback")
.setContentIntent( pendingOpenAppIntent )
.setStyle( androidx.media.app.NotificationCompat.MediaStyle()
.setMediaSession(mediaSession.sessionToken)
.setShowActionsInCompactView(1, 2, 3)
)
.setSmallIcon(R.drawable.icon)
.addAction(R.drawable.icon_x, "Stop Player", pendingStopIntent )
.addAction(R.drawable.icon_previous_notification, "Previous Music", pendingPreviousSongIntent )
.addAction(R.drawable.icon_pause_notification, "Play Pause Music", pendingPlayPauseIntent )
.addAction(R.drawable.icon_next_notification, "Next Music", pendingSkipSongIntent )
.build()
mediaSession.setMetadata(
MediaMetadataCompat.Builder()
.putString(MediaMetadata.METADATA_KEY_TITLE, songTitle)
.putString(MediaMetadata.METADATA_KEY_ARTIST, songArtist)
.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, songAlbumArt)
.putLong(MediaMetadata.METADATA_KEY_DURATION, songDuration.toLong())
.build()
)
startForeground( 2, notification )
notificationManager.notify( 2, notification )
}
handleSongFinished( context )
if( !musicShuffled ) {
onMusicSelectedListener?.onMusicSelected(playList, currentSongPosition)
onMusicSelectedListenerToQueue?.onMusicSelected(playList, currentSongPosition)
}
else {
onMusicSelectedListener?.onMusicSelected(shuffledPlaylist, currentSongPosition)
onMusicSelectedListenerToQueue?.onMusicSelected(shuffledPlaylist, currentSongPosition)
}
val bluetoothReceiver = IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
context.registerReceiver(bluetoothBroadcastReceiver, bluetoothReceiver )
val mainHandler = Handler(Looper.getMainLooper())
mainHandler.post( object : Runnable{
override fun run() {
if( onMusicSecondPassedListener != null )
onMusicSecondPassedListener?.onSecondPassed( mediaPlayer.currentPosition )
mainHandler.postDelayed( this,1000)
}
})
}
private val bluetoothBroadcastReceiver = object : BroadcastReceiver(){
override fun onReceive(p0: Context?, p1: Intent?) {
if(isMusicPlaying()) pauseMusic(p0!!)
}
}
fun seekTo( position: Int){
val newSongPosition = position * 1000
mediaPlayer.seekTo(newSongPosition)
if( !mediaPlayer.isPlaying ) mediaPlayer.start()
}
private fun handleSongFinished(context: Context) {
mediaPlayer.setOnCompletionListener{
//If loop mode is activated
if( onRepeatMode ){
playSong( context )
}
//Is it's the last song
else if( (currentSongPosition + 1) == playList.size ){
stopMediaPlayer()
}
else{
currentSongPosition++
playSong( context )
}
}
}
fun toggleLoop(){
onRepeatMode = !onRepeatMode
}
fun stopMediaPlayer(){
onMediaPlayerStoppedListener?.onMediaPlayerStopped()
mediaPlayer.stop()
currentSongPosition = -1
currentSongPath = ""
stopForeground(true)
stopSelf()
}
fun skipSong(context: Context){
if( (currentSongPosition + 1) < playList.size ){
currentSongPosition ++
playSong( context )
}
}
fun previousSong(context: Context){
if( (currentSongPosition - 1) >= 0 ){
currentSongPosition--
playSong( context )
}
}
#Suppress("DEPRECATION")
fun pauseMusic(context: Context ){
val playPauseIcon = R.drawable.icon_play_notification
mediaPlayer.pause()
mediaSession.isActive = false
if( onMusicPausedListener != null)
onMusicPausedListener?.onMusicPaused()
//Updates the notification
val playPauseIntent = Intent(context, ReceiverPlayPause::class.java )
playPauseIntent.putExtra( "action", "playPause" )
val pendingPlayPauseIntent = PendingIntent.getBroadcast( context, 1, playPauseIntent, PendingIntent.FLAG_IMMUTABLE )
notification.actions[2] = Notification.Action( playPauseIcon, "Play Music", pendingPlayPauseIntent )
startForeground( 2, notification )
notificationManager.notify( 2, notification )
}
#Suppress("DEPRECATION")
fun pauseResumeMusic(context: Context ){
val playPauseIcon: Int
if( mediaPlayer.isPlaying ) {
playPauseIcon = R.drawable.icon_play_notification
mediaPlayer.pause()
if( onMusicPausedListener != null) onMusicPausedListener?.onMusicPaused()
}
else {
playPauseIcon = R.drawable.icon_pause_notification
if( onMusicResumedListener != null ) onMusicResumedListener?.onMusicResumed()
requestPlayWithFocus()
}
//Updates the notification
val playPauseIntent = Intent(context, ReceiverPlayPause::class.java )
playPauseIntent.putExtra( "action", "playPause" )
val pendingPlayPauseIntent = PendingIntent.getBroadcast( context, 1, playPauseIntent, PendingIntent.FLAG_IMMUTABLE )
notification.actions[2] = Notification.Action( playPauseIcon, "Play Music", pendingPlayPauseIntent )
startForeground( 2, notification )
notificationManager.notify( 2, notification )
}
private fun requestPlayWithFocus(){
val focusLock = Any()
val res = audioManager.requestAudioFocus(focusRequest)
synchronized(focusLock) {
when (res) {
AudioManager.AUDIOFOCUS_REQUEST_GRANTED -> {
mediaPlayer.start()
onMusicResumedListener?.onMusicResumed()
true
}
else -> false
}
}
}
fun updatePlaylists(){ onPlaylistAdded?.onPlaylistAdded() }
//////////////////////////////////////////////////////////////////////////////////////////////////////
interface OnMusicSelectedListener{ fun onMusicSelected(playList: ArrayList<Song>, position: Int ) }
interface OnMusicSelectedListenerToQueue{ fun onMusicSelected(playList: ArrayList<Song>, position: Int ) }
interface OnPlaylistsAdded{ fun onPlaylistAdded() }
interface OnMusicPausedListener{ fun onMusicPaused() }
interface OnMusicResumedListener{ fun onMusicResumed() }
interface OnSecondPassedListener{ fun onSecondPassed(position: Int ) }
interface OnMusicShuffleToggledListener{ fun onMusicShuffleToggled(state: Boolean) }
interface OnMediaPlayerStoppedListener{ fun onMediaPlayerStopped() }
}
I wanted to make other devices recognize what song is playing and map the respective buttons like previous pause play next. I tried to search but didn't find anything useful. I don't know what to search specifically.
Thanks in advance :D
I finally found the problem. I wass missing Playback State And Callbacks.
I am sharing what i did in case anyone ends up here and in the same situation.
First i added other callbacks to media session callback. Mainly this ones:
override fun onPlay() {
super.onPlay()
pauseResumeMusic(context)
}
override fun onStop() {
super.onStop()
stopMediaPlayer()
}
override fun onPause() {
super.onPause()
pauseResumeMusic(context)
}
override fun onSkipToNext() {
super.onSkipToNext()
selectNextSong(context)
}
override fun onSkipToPrevious() {
super.onSkipToPrevious()
selectPreviousSong(context)
}
After that i needed to set the state when those actions happened. For that i made a function to set it.
fun setPlaybackState(state: Int){
val stateBuilder = PlaybackStateCompat.Builder()
.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE
or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
or PlaybackStateCompat.ACTION_SKIP_TO_NEXT)
.apply {
setState(state, mediaPlayer.currentPosition.toLong(), 1.0f)
}
mediaSession.setPlaybackState(stateBuilder.build())
}
Now everytime i need to update i just call the function and send the state as a parameter.
Example:
setPlaybackState(PlaybackStateCompat.STATE_PLAYING)
Hope this helps someone in case it was stuck like me for months XD
I try to study overlay library for kotlin applications https://github.com/KoderLabs/overlay-service. Right now I have a problem related with opening a new activity from a button which is located inside a FrameLayout class.
So, the task is push on button on Main Activity -> open new overlay small window and roll up Main Activity -> push on button inside a small window -> open new Activity.
In this code nothing happens after the clicking on button.
The project include main 3 classes:
MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btn_simple_pip.setOnClickListener {
checkDrawOverlayPermission(IMPLEMENTED_PIP_OVERLAY_REQUEST_CODE)
finishAffinity()
}
}
private fun checkDrawOverlayPermission(code: Int) {
if (Build.VERSION.SDK_INT >= 23) {
if (!Settings.canDrawOverlays(this)) {
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:$packageName"))
startActivityForResult(intent, IMPLEMENTED_PIP_OVERLAY_REQUEST_CODE)
} else {
openFloatingWindow(code)
}
} else {
openFloatingWindow(code)
}
}
private fun openFloatingWindow(code: Int) {
when (code) {
IMPLEMENTED_PIP_OVERLAY_REQUEST_CODE -> {
val intent = Intent(this, ImplementPipOverlayService::class.java)
val videoUrl =
"https://s3.amazonaws.com/data.development.momentpin.com/2019/7/3/1562152168485485-0661a550-9d83-11e9-9028-d7af09cf782e.mp4"
val notificationTitle = "Pip Overlay"
val notificationDescription = "Pip overlay description"
val notificationIcon = R.drawable.ic_launcher_foreground
val closeBtnColor = android.R.color.black
val closeBtnBgColor = android.R.color.transparent
intent.putExtra(ImplementPipOverlayService.KEY_STRING_VIDEO_URL, videoUrl)
intent.putExtra(ImplementPipOverlayService.KEY_STRING_NOTIFICATION_DESCRIPTION, notificationDescription)
intent.putExtra(ImplementPipOverlayService.KEY_STRING_NOTIFICATION_TITLE, notificationTitle)
intent.putExtra(ImplementPipOverlayService.KEY_INT_NOTIFICATION_ICON, notificationIcon)
intent.putExtra(ImplementPipOverlayService.KEY_INT_CLOSE_BUTTON_COLOR, closeBtnColor)
intent.putExtra(ImplementPipOverlayService.KEY_INT_CLOSE_BUTTON_BG_COLOR, closeBtnBgColor)
ContextCompat.startForegroundService(this, intent)
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
IMPLEMENTED_PIP_OVERLAY_REQUEST_CODE, RESIZEABLE_CUSTOM_WEB_OVERLAY_REQUEST_CODE,
PIP_OVERLAY_REQUEST_CODE, RESIZEABLE_CUSTOM_VIDEO_OVERLAY_REQUEST_CODE -> {
if (Build.VERSION.SDK_INT >= 23) {
if (Settings.canDrawOverlays(this)) {
openFloatingWindow(requestCode)
}
} else {
openFloatingWindow(requestCode)
}
}
}
super.onActivityResult(requestCode, resultCode, data)
}
companion object {
const val IMPLEMENTED_PIP_OVERLAY_REQUEST_CODE = 251
const val PIP_OVERLAY_REQUEST_CODE = 252
const val RESIZEABLE_CUSTOM_VIDEO_OVERLAY_REQUEST_CODE = 253
const val RESIZEABLE_CUSTOM_WEB_OVERLAY_REQUEST_CODE = 254
}
ImplementPipOverlayService
class ImplementPipOverlayService : PipOverlayService() {
var videoUrl: String? = null
var notificationTitle: String? = null
var notificationDescription: String? = null
var notificationIcon: Int? = null
var closeButtonColor: Int? = null
var closeButtonBg: Int? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
videoUrl = intent?.getStringExtra(KEY_STRING_VIDEO_URL)
notificationTitle = intent?.getStringExtra(KEY_STRING_NOTIFICATION_TITLE)
notificationDescription = intent?.getStringExtra(KEY_STRING_NOTIFICATION_DESCRIPTION)
notificationIcon = intent?.getIntExtra(KEY_INT_NOTIFICATION_ICON, -1)
closeButtonColor = intent?.getIntExtra(KEY_INT_CLOSE_BUTTON_COLOR, -1)
closeButtonBg = intent?.getIntExtra(KEY_INT_CLOSE_BUTTON_BG_COLOR, -1)
return super.onStartCommand(intent, flags, startId)
}
override fun getForegroundNotification(): Notification {
val channelId =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel("my_service", "My Background Service")
} else {
packageName
}
var notificationBuilder = NotificationCompat.Builder(this, channelId)
notificationBuilder = notificationTitle?.let {
notificationBuilder.setContentTitle(it)
} ?: run {
notificationBuilder.setContentTitle("Title")
}
notificationDescription?.let {
notificationBuilder = notificationBuilder.setContentText(it)
}
notificationIcon?.let {
notificationBuilder = notificationBuilder.setSmallIcon(it)
}
val notification: Notification = notificationBuilder.build()
return notification
}
#RequiresApi(Build.VERSION_CODES.O)
private fun createNotificationChannel(channelId: String, channelName: String): String {
val chan = NotificationChannel(channelId,
channelName, NotificationManager.IMPORTANCE_NONE)
chan.lightColor = Color.BLUE
chan.lockscreenVisibility = Notification.VISIBILITY_PRIVATE
val service = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
service.createNotificationChannel(chan)
return channelId
}
override fun getInitialWindowSize(): Point {
return Point(100.toDp(), 100.toDp())
}
override fun getCustomLayoutId(): Int {
return R.layout.pip_layout
}
override fun onServiceRun() {
setOnEventListener(onFullscreen = {
// Not implemented
}, onClosed = {
// Not implemented
})
pipView.removeFullscreenButton()
closeButtonColor?.let {
pipView.getCloseButton().setColorFilter(it)
}
closeButtonBg?.let {
pipView.getCloseButton().setBackgroundColor(it)
}
}
companion object {
const val KEY_STRING_VIDEO_URL = "video_url"
const val KEY_INT_CLOSE_BUTTON_COLOR = "close_button_color"
const val KEY_INT_CLOSE_BUTTON_BG_COLOR = "close_button_background"
const val KEY_STRING_NOTIFICATION_TITLE = "notification_title"
const val KEY_STRING_NOTIFICATION_DESCRIPTION = "notification_description"
const val KEY_INT_NOTIFICATION_ICON = "notification_icon"
}
OverlayPipCustomView
class OverlayPipCustomView : FrameLayout {
private lateinit var constraintsRoot: ConstraintLayout
private lateinit var imageFullscreenButton: ImageView
private lateinit var imageCloseButton: ImageView
private lateinit var customLayoutContent: FrameLayout
private lateinit var customView: View
private lateinit var touchView: View
private lateinit var button: Button
private var playerViewSize: Int = 0
private var sizeChangeable: Boolean = true
private var playerType: Int = 0
private var haveFullscreen = true
/**
* Tracks if viewResizeable is fullscreen.
*/
private var fullscreenOn: Boolean = false
val isDraggable: Boolean
get() {
return !fullscreenOn
}
private var onFullscreen: () -> Unit = {}
private var onClosed: () -> Unit = {}
private var canHideActionButtons = true
private val hideActionHandler = Handler()
private val HIDE_ACTION_DURATION = 2000L
private val hideActionRunnable = Runnable {
if (!isMoving) {
hideActions()
}
}
var isMoving: Boolean = false
constructor(ctx: Context) : super(ctx) {
inflate(context, R.layout.layout_pip_custom_view, this)
initView()
}
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs) {
setAttributes(attrs)
inflate(context, R.layout.layout_pip_custom_view, this)
initView()
}
constructor(ctx: Context, attrs: AttributeSet, defStyle: Int) : super(ctx, attrs, defStyle) {
setAttributes(attrs)
inflate(context, R.layout.layout_pip_custom_view, this)
initView()
}
private fun setAttributes(attrs: AttributeSet) {
context.theme.obtainStyledAttributes(
attrs,
R.styleable.OverlayCustomView,
0, 0).apply {
try {
playerViewSize = getInteger(R.styleable.OverlayCustomView_ov_player_size, 0)
playerType = getInteger(R.styleable.OverlayCustomView_ov_size_changeable, 0)
sizeChangeable = getBoolean(R.styleable.OverlayCustomView_ov_size_changeable, true)
} finally {
recycle()
}
}
doOnLayout {
startHideAction()
}
}
private fun initView() {
button = findViewById(R.id.button)
constraintsRoot = findViewById(R.id.constraints_root)
imageFullscreenButton = findViewById(R.id.image_screen_action)
imageCloseButton = findViewById(R.id.image_close)
customLayoutContent = findViewById(R.id.custom_view)
touchView = findViewById(R.id.touch_view)
setListeners()
}
fun addCustomView(view: View) {
customLayoutContent.addView(view)
}
fun addCustomView(layoutId: Int) {
customView = inflate(context, layoutId, null)
customLayoutContent.addView(customView)
}
fun getCloseButton() = imageCloseButton
fun getConstraintsRoot() = constraintsRoot
fun getCustomLayoutContent() = customLayoutContent
fun getCustomView() = customView
fun getFullscreenButton() = imageFullscreenButton
fun getTouchView() = touchView
fun removeFullscreenButton() {
haveFullscreen = false
imageFullscreenButton.invisible()
}
private fun setListeners() {
imageFullscreenButton.setOnClickListener {
onFullscreen.invoke()
}
button.setOnClickListener{
println("pressed")
fun alert(context: Context, text: String) {
val intent = Intent(context, MainActivity2::class.java)
intent.putExtra("text", text)
context.startActivity(intent)
}
}
imageCloseButton.setOnClickListener {
onClosed.invoke()
}
}
fun setOnEventActionListener(
onFullscreen: () -> Unit,
onClosed: () -> Unit
) {
this.onFullscreen = onFullscreen
this.onClosed = onClosed
}
private fun startHideAction() {
if (canHideActionButtons) {
hideActionHandler.postDelayed(hideActionRunnable, HIDE_ACTION_DURATION)
}
}
fun restartHideAction() {
hideActionHandler.removeCallbacks(hideActionRunnable)
if (canHideActionButtons) {
hideActionHandler.postDelayed(hideActionRunnable, HIDE_ACTION_DURATION)
}
}
fun hideActions() {
if (canHideActionButtons) {
imageCloseButton.invisible()
if (haveFullscreen) {
imageFullscreenButton.invisible()
}
}
}
fun showActions() {
imageCloseButton.visible()
if (haveFullscreen) {
imageFullscreenButton.visible()
}
}
}
In third class I can't understand what should be placed here to to change initial first Activity to another one
button.setOnClickListener{
println("pressed")
fun alert(context: Context, text: String) {
val intent = Intent(context, MainActivity2::class.java)
intent.putExtra("text", text)
context.startActivity(intent)
}
}
startService(intent) will try and start a new Service and unfortunately does not throw an error if you call it with an Activity class.
To launch MainActivity2 instead call context.startActivity(intent).
I have a Firestore database holding my data. Here is the structure
(database structure)
subclass database structure
So my booking is sub-collections of the user. Each booking contains its own restaurant as a sub-document.
Here is the activity file:
private lateinit var binding: ActivityMyBookingsBinding
//recyclerview for list
lateinit var recyclerView: RecyclerView
//Firestore
private var currentUser: FirebaseUser = FirebaseAuth.getInstance().currentUser!!
private var db: FirebaseFirestore = FirebaseFirestore.getInstance()
var query: CollectionReference = db
.collection("Users")
.document(currentUser.uid)
.collection("Bookings")
//adapter
lateinit var bookingAdapter: BookingItemAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMyBookingsBinding.inflate(layoutInflater)
val view = binding.root
setupUI()
setContentView(view)
setupRecyclerView()
}
private fun setupUI() {
recyclerView = binding.bookingList
}
private fun setupRecyclerView(){
val options: FirestoreRecyclerOptions<BookingItem> = FirestoreRecyclerOptions.Builder<BookingItem>()
.setQuery(query, BookingItem::class.java)
.build()
bookingAdapter = BookingItemAdapter(this, options)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = bookingAdapter
}
override fun onStart() {
super.onStart()
bookingAdapter.startListening()
}
override fun onStop() {
super.onStop()
bookingAdapter.stopListening()
}
Here is the adapter file
class BookingItemAdapter(
val context: Context,
val options: FirestoreRecyclerOptions<BookingItem>): FirestoreRecyclerAdapter<BookingItem, BookingItemAdapter.BookingViewHolder>(options) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BookingViewHolder {
return BookingViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.card_booking,
parent,
false,
),
)
}
#RequiresApi(Build.VERSION_CODES.N)
override fun onBindViewHolder(holder: BookingViewHolder, position: Int, model: BookingItem) {
holder.restaurantNameText.booking_restaurant_name.text = model.getRestaurantItem().getName()
holder.restaurantDistanceText.booking_distance.text = model.getRestaurantItem().getGeoHash()
holder.restaurantRatingBar.booking_ratingBar.rating = model.getRestaurantItem().getRating()?.toFloat()!!
holder.bookingDate.booking_date.text = "${model.getDay()}/${model.getMonth()}/${model.getYear()}"
holder.bookingTime.booking_time.text = "${model.getHour()}:${model.getMinute()}"
holder.numberGuests.number_guests.text = model.getGuestNumber()
val url = model.getRestaurantItem().getRestaurantImage()
Glide
.with(holder.restaurantImageItem)
.load(url)
.into(holder.restaurantImageItem.booking_restaurantImage)
holder.itemView.setOnClickListener {
val intent = Intent(context, RestaurantPageActivity::class.java)
intent.putExtra("model", model.getRestaurantItem())
context.startActivity(intent)
}
CompletableFuture.runAsync {
runCatching {
val url = model.getRestaurantItem().getRestaurantImage()
Glide
.with(holder.restaurantImageItem)
.load(url)
.into(holder.restaurantImageItem.booking_restaurantImage)
}
}
}
inner class BookingViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val restaurantNameText: TextView = itemView.findViewById(R.id.booking_restaurant_name)
val restaurantImageItem: ImageView = itemView.findViewById(R.id.booking_restaurantImage)
val restaurantRatingBar: RatingBar = itemView.findViewById(R.id.booking_ratingBar)
val restaurantDistanceText: TextView = itemView.findViewById(R.id.booking_distance)
val bookingTime: TextView = itemView.findViewById(R.id.booking_time)
val bookingDate: TextView = itemView.findViewById(R.id.booking_date)
val numberGuests: TextView = itemView.findViewById(R.id.number_guests)
}
Here is the bookingItem class:
class BookingItem(restaurantItem: RestaurantItem, guestNumber: String, day: String,
month: String, year: String, hour: String, minute: String) {
constructor(): this(RestaurantItem("", "", 0, 0,
"", "", "", "", false), "", "", "", "", "","")
private var restaurantItemName = restaurantItem.getName()
private var restaurantItemImage = restaurantItem.getRestaurantImage()
private var restaurantItemPrice = restaurantItem.getPrice()
private var restaurantItemRating = restaurantItem.getRating()
private var restaurantItemGeohash = restaurantItem.getGeoHash()
private var restaurantItemLongitude = restaurantItem.getLongitude()
private var restaurantItemLatitude = restaurantItem.getLatitude()
private var restaurantItemCuisine = restaurantItem.getCuisine()
private var restaurantItemDietary = restaurantItem.getDietaryFriendly()
private var restaurantItem: RestaurantItem = RestaurantItem(
restaurantItemName,
restaurantItemImage,
restaurantItemPrice,
restaurantItemRating,
restaurantItemGeohash,
restaurantItemLongitude,
restaurantItemLatitude,
restaurantItemCuisine,
restaurantItemDietary)
private var guestNumber: String = guestNumber
private var day: String = day
private var month: String = month
private var year: String = year
private var hour: String = hour
private var minute: String = minute
fun getRestaurantItem(): RestaurantItem{
this.restaurantItem.getName()
this.restaurantItem.getRestaurantImage()
this.restaurantItem.getPrice()
this.restaurantItem.getRating()
this.restaurantItem.getGeoHash()
this.restaurantItem.getLongitude()
this.restaurantItem.getLatitude()
this.restaurantItem.getCuisine()
this.restaurantItem.getDietaryFriendly()
return this.restaurantItem
}
fun getGuestNumber(): String {
return this.guestNumber
}
fun getDay(): String{
return this.day
}
fun getMonth(): String{
return this.month
}
fun getYear(): String{
return this.year
}
fun getHour(): String{
return this.hour
}
fun getMinute(): String{
return this.minute
}
}
Finally incase you need it, here is the sub entity - restaurantItem class:
class RestaurantItem(name: String, restaurantImage: String, price: Long, rating: Long, geoHash: String ,
longitude: String, latitude: String, cuisine: String, dietaryFriendly: Boolean): Parcelable{
constructor(): this ("", "", 0, 0,
"", "", "", "", false
)
private var name: String = name
private var restaurantImage: String = restaurantImage
private var price: Long = price
private var rating: Long = rating
private var geoHash: String = geoHash
private var longitude: String = longitude
private var latitude: String = latitude
private var cuisine: String = cuisine
private var dietaryFriendly: Boolean = dietaryFriendly
private constructor(parcel: Parcel) : this() {
name = parcel.readString().toString()
restaurantImage = parcel.readString().toString()
price = parcel.readLong()
rating = parcel.readLong()
geoHash = parcel.readString().toString()
longitude = parcel.readString().toString()
latitude = parcel.readString().toString()
cuisine = parcel.readString().toString()
dietaryFriendly = parcel.readByte() != 0.toByte()
}
fun getName(): String {
return this.name
}
fun getLatitude(): String {
return this.latitude
}
fun getLongitude(): String {
return this.longitude
}
fun getGeoHash(): String {
return this.geoHash
}
fun getPrice(): Long {
return this.price
}
fun getDietaryFriendly(): Boolean{
return this.dietaryFriendly
}
fun getRating(): Long {
return this.rating
}
fun getRestaurantImage(): String {
return this.restaurantImage
}
fun getCuisine(): String {
return this.cuisine
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(name)
parcel.writeString(restaurantImage)
parcel.writeLong(price)
parcel.writeLong(rating)
parcel.writeString(geoHash)
parcel.writeString(longitude)
parcel.writeString(latitude)
parcel.writeString(cuisine)
parcel.writeByte(if (dietaryFriendly) 1 else 0)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<RestaurantItem> {
override fun createFromParcel(parcel: Parcel): RestaurantItem {
return RestaurantItem(parcel)
}
override fun newArray(size: Int): Array<RestaurantItem?> {
return arrayOfNulls(size)
}
}
Now the data is coming in from firebase fine and the time, date, and a number of guests are being displayed correctly.
The restaurant class works because it is used elsewhere and displays correctly in other recycler views.
The issue seems to be that when the booking item is instanciated, all the data in the restaurantItem gets set to its null values.
I walked through with the debugger and I can see the data has made it from firebase.
This is the data when I debug in the bookingitem constructor and step through
This is where the data is lost and reset to null values:
Here is the line that the data is lost
You can access the code here https://github.com/KotaCanchela/RestaurantBookingSystem
I appreciate any help that can be provided, I've tried to give as much detail here as possible.
When you are debugging your code, all the properties of the "restaurantItem" object in the debugger are holding the default values because there is no match between the object of the class and the object in Firestore.
Your "BookingItem" class holds the property called "restaurantItem", while in the database the object is called only "restaurant", which is not correct. Both names must match. You either change the name of the property in the database to be "restaurantItem" and not just "restaurant", or you should use the following annotations:
#get:PropertyName("restaurant")
#set:PropertyName("restaurant")
#PropertyName("restaurant")
In front of your restaurantItem property in your "BookingItem" class.
Edit:
According to your comment:
do you mean in the BookingItem constructor?
Yes. The minimum implementation for your class might be:
data class BookingItem(
#get:PropertyName("restaurant")
#set:PropertyName("restaurant")
#PropertyName("restaurant")
var restaurantItem: RestaurantItem? = null,
var guestNumber: String? = null,
var day: String? = null,
var month: String? = null,
var year: String? = null,
var hour: String? = null,
var minute: String? = null
)
Where I have initialized those objects with a null value. Once you get the data from the database, you'll assign the values to your actual fields.
I'm Joss this is my first question in stackoverflow
I want to activate(isEnabled = true) the button when a geofence event occurs.
The code that works sendNotification is woriking but I want to add function add button(btn_done) activate
GeofenceTransitionService.kt
class GeofenceTransitionService : IntentService("GeoTrIntentService") {
companion object {
private const val LOG_TAG = "GeoTrIntentService"
}
override fun onHandleIntent(intent: Intent?) {
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
val errorMessage = GeofenceErrorMessages.getErrorString(this,
geofencingEvent.errorCode)
Log.e(LOG_TAG, errorMessage)
return
}
handleEvent(geofencingEvent)
}
private fun handleEvent(event: GeofencingEvent) {
if (event.geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) {
btn_done.isEnabled = true //How should I code this?
val reminder = getFirstReminder(event.triggeringGeofences)
val message = reminder?.message
val latLng = reminder?.latLng
if (message != null && latLng != null) {
sendNotification(this, message, latLng)
}
}
}
private fun getFirstReminder(triggeringGeofences: List<Geofence>): Reminder? {
val firstGeofence = triggeringGeofences[0]
return (application as
ReminderApp).getRepository().get(firstGeofence.requestId)
}
}
ReminderRepository.kt
class ReminderRepository(private val context: Context) {
companion object {
private const val PREFS_NAME = "ReminderRepository"
private const val REMINDERS = "REMINDERS"
}
private val preferences = context.getSharedPreferences(PREFS_NAME,
Context.MODE_PRIVATE)
private val gson = Gson()
private val geofencingClient = LocationServices.getGeofencingClient(context)
private val geofencePendingIntent: PendingIntent by lazy {
val intent = Intent(context, GeofenceTransitionService::class.java)
PendingIntent.getService(
context,
0,
intent,
PendingIntent.FLAG_UPDATE_CURRENT)
}
fun add(reminder: Reminder,
success: () -> Unit,
failure: (error: String) -> Unit) {
// 1
val geofence = buildGeofence(reminder)
if (geofence != null
&& ContextCompat.checkSelfPermission(
context,
Manifest.permission.ACCESS_FINE_LOCATION) ==
PackageManager.PERMISSION_GRANTED) {
// 2
geofencingClient
.addGeofences(buildGeofencingRequest(geofence),
geofencePendingIntent)
.addOnSuccessListener {
// 3
saveAll(getAll() + reminder)
success()
}
.addOnFailureListener {
// 4
failure(GeofenceErrorMessages.getErrorString(context,
it))
}
}
}
private fun buildGeofence(reminder: Reminder): Geofence? {
val latitude = reminder.latLng?.latitude
val longitude = reminder.latLng?.longitude
val radius = reminder.radius
if (latitude != null && longitude != null && radius != null) {
return Geofence.Builder()
.setRequestId(reminder.id)
.setCircularRegion(
latitude,
longitude,
radius.toFloat()
)
.setTransitionTypes(Geofence.GEOFENCE_TRANSITION_ENTER)
.setExpirationDuration(Geofence.NEVER_EXPIRE)
.build()
}
return null
}
private fun buildGeofencingRequest(geofence: Geofence): GeofencingRequest {
return GeofencingRequest.Builder()
.setInitialTrigger(0)
.addGeofences(listOf(geofence))
.build()
}
fun remove(reminder: Reminder,
success: () -> Unit,
failure: (error: String) -> Unit) {
geofencingClient
.removeGeofences(listOf(reminder.id))
.addOnSuccessListener {
saveAll(getAll() - reminder)
success()
}
.addOnFailureListener {
failure(GeofenceErrorMessages.getErrorString(context, it))
}
}
private fun saveAll(list: List<Reminder>) {
preferences
.edit()
.putString(REMINDERS, gson.toJson(list))
.apply()
}
fun getAll(): List<Reminder> {
if (preferences.contains(REMINDERS)) {
val remindersString = preferences.getString(REMINDERS, null)
val arrayOfReminders = gson.fromJson(remindersString,
Array<Reminder>::class.java)
if (arrayOfReminders != null) {
return arrayOfReminders.toList()
}
}
return listOf()
}
fun get(requestId: String?) = getAll().firstOrNull { it.id == requestId }
fun getLast() = getAll().lastOrNull()
}
MainActivity.kt (show only important code)
class MainActivity : BaseActivity(), OnMapReadyCallback,
GoogleMap.OnMarkerClickListener {
companion object {
private const val MY_LOCATION_REQUEST_CODE = 329
private const val NEW_REMINDER_REQUEST_CODE = 330
private const val EXTRA_LAT_LNG = "EXTRA_LAT_LNG"
private const val LOG_TAG = "GeoTrIntentService"
fun newIntent(context: Context, latLng: LatLng): Intent {
val intent = Intent(context, MainActivity::class.java)
intent.putExtra(EXTRA_LAT_LNG, latLng)
return intent
}
}
private var map: GoogleMap? = null
private lateinit var locationManager: LocationManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_maps)
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
newReminder.visibility = View.GONE
currentLocation.visibility = View.GONE
newReminder.setOnClickListener {
map?.run {
val intent = NewReminderActivity.newIntent(
this#MainActivity,
cameraPosition.target,
cameraPosition.zoom)
startActivityForResult(intent, NEW_REMINDER_REQUEST_CODE)
val geofencingEvent = GeofencingEvent.fromIntent(intent)
qwer(geofencingEvent)
}
}
btn_done.setOnClickListener {
val intent = Intent(this, sub::class.java)
startActivity(intent)
}
locationManager = getSystemService(Context.LOCATION_SERVICE) as
LocationManager
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
MY_LOCATION_REQUEST_CODE)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data:
Intent?) {
if (requestCode == NEW_REMINDER_REQUEST_CODE && resultCode ==
Activity.RESULT_OK) {
showReminders()
val reminder = getRepository().getLast()
map?.moveCamera(CameraUpdateFactory.newLatLngZoom(reminder?.latLng,
15f))
Snackbar.make(main, R.string.reminder_added_success,
Snackbar.LENGTH_LONG).show()
}
}
activate_maps.xml
<Button
android:id="#+id/btn_done"
android:enabled="false"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Arrive" />
You can use kotlin interface to change button state in app.
Create Interface for button state change in GeofenceTransitionService.kt
class GeofenceTransitionService : IntentService("GeoTrIntentService") {
interface ChangeViewState {
fun changeButtonState() : Button
}
companion object {
private const val LOG_TAG = "GeoTrIntentService"
}
override fun onHandleIntent(intent: Intent?) {
val geofencingEvent = GeofencingEvent.fromIntent(intent)
if (geofencingEvent.hasError()) {
val errorMessage = GeofenceErrorMessages.getErrorString(this,
geofencingEvent.errorCode)
Log.e(LOG_TAG, errorMessage)
return
}
handleEvent(geofencingEvent)
}
private val changeViewState : ChangeViewState? = null
private fun handleEvent(event: GeofencingEvent) {
if (event.geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER) {
//btn_done.isEnabled = true //How should I code this?
changeViewState?.changeButtonState().isEnabled = true
val reminder = getFirstReminder(event.triggeringGeofences)
val message = reminder?.message
val latLng = reminder?.latLng
if (message != null && latLng != null) {
sendNotification(this, message, latLng)
}
}
}
private fun getFirstReminder(triggeringGeofences: List<Geofence>): Reminder? {
val firstGeofence = triggeringGeofences[0]
return (application as
ReminderApp).getRepository().get(firstGeofence.requestId)
}
}
Implement interface in main activity
class MainActivity : BaseActivity(), OnMapReadyCallback,
GoogleMap.OnMarkerClickListener, GeofenceTransitionService.ChangeViewState {
companion object {
private const val MY_LOCATION_REQUEST_CODE = 329
private const val NEW_REMINDER_REQUEST_CODE = 330
private const val EXTRA_LAT_LNG = "EXTRA_LAT_LNG"
private const val LOG_TAG = "GeoTrIntentService"
fun newIntent(context: Context, latLng: LatLng): Intent {
val intent = Intent(context, MainActivity::class.java)
intent.putExtra(EXTRA_LAT_LNG, latLng)
return intent
}
}
private var map: GoogleMap? = null
private lateinit var locationManager: LocationManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_maps)
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
// Button Ref
newReminder.visibility = View.GONE
currentLocation.visibility = View.GONE
newReminder.setOnClickListener {
map?.run {
val intent = NewReminderActivity.newIntent(
this#MainActivity,
cameraPosition.target,
cameraPosition.zoom
)
startActivityForResult(intent, NEW_REMINDER_REQUEST_CODE)
val geofencingEvent = GeofencingEvent.fromIntent(intent)
qwer(geofencingEvent)
}
}
btn_done.setOnClickListener {
val intent = Intent(this, sub::class.java)
startActivity(intent)
}
locationManager = getSystemService(Context.LOCATION_SERVICE) as
LocationManager
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
)
!= PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
MY_LOCATION_REQUEST_CODE
)
}
}
override fun onActivityResult(
requestCode: Int, resultCode: Int, data:
Intent?
) {
if (requestCode == NEW_REMINDER_REQUEST_CODE && resultCode ==
Activity.RESULT_OK
) {
showReminders()
val reminder = getRepository().getLast()
map?.moveCamera(
CameraUpdateFactory.newLatLngZoom(
reminder?.latLng,
15f
)
)
Snackbar.make(
main, R.string.reminder_added_success,
Snackbar.LENGTH_LONG
).show()
}
}
// Implement interface
override fun changeButtonState(): Button {
val button = findViewById(R.id.btn_done) as Button
return button
}
}