how to detect volume change in kotlin - kotlin

I want to change the seekbar position according to the volume size. It was possible to change the seekbar position according to the volume level when the volume button is pressed, but I do not know how to change the seekbar position when the volume level changes regardless of the button press.
I tried the below but doesn't work.
MainActivity.kt
class MainActivity : AppCompatActivity() {
var volumeBroadcastReceiver = object: BroadcastReceiver(){
override fun onReceive(p0: Context?, p1: Intent?) {
var action = intent?.getAction()
if (action != null) {
if(action.equals("android.media.VOLUME_CHANGED_ACTION")){
System.out.println("volume changed")
}
}
}
}
var filter = IntentFilter().apply{
state("android.media.VOLUME_CHANGED_ACTION")
}
registerReceiver(volumeBroadcastReceiver,filter)
}

Related

Change the color of the Recycler view item and return to the original state in kotlin

I have a Recycler view to display a custom calendar and everything works fine
My problem is: when I click on one item, the color of the item changes, but when I click on another item, the previous item does not return to default.
my code :
class CalendarAdapter(val clickListener: (CalendarModel) -> Unit) :
ListAdapter<CalendarModel, CalendarAdapter.CalendarViewHolder>(CalendarDiffUtils()) {
private var select = -1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CalendarViewHolder {
val binding =
CalendarItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return CalendarViewHolder(binding, binding.root)
}
override fun onBindViewHolder(holder: CalendarViewHolder, position: Int) {
holder.onBind(getItem(position))
}
override fun getItemViewType(position: Int): Int {
return position
}
inner class CalendarViewHolder(
private val binding: CalendarItemBinding,
containerView: View
) :
RecyclerView.ViewHolder(containerView) {
fun onBind(dateModel: CalendarModel) {
with(dateModel) {
with(binding) {
//Show empty days.
txtIranianDate.isVisible = iranianDay != EMPTY_DATE
txtGregorianDate.isVisible = iranianDay != EMPTY_DATE
if (iranianDay == EMPTY_DATE) {
return
}
//Click
itemView.setOnClickListener {
clickListener(dateModel)
select = adapterPosition // <== select:Int = -1
//Change color With click
if (select == adapterPosition){ // <== Here I want change color.
cardDays.setCardBackgroundColor(
ContextCompat.getColor(
itemView.context,
R.color.blue
)
)
}else{ // <== back to the default color.
cardDays.setCardBackgroundColor(
ContextCompat.getColor(
itemView.context,
R.color.white
)
)
}
I have removed the additional codes related to the DiffUtils classes.
The reason this isn't working is that you only change it to blue when an item is clicked, but there's no way to ever change it back because you are only changing it back in the click listener. Also, views are recycled, so each time something scrolls into view, it is getting a view that may have a blue or white background and you're not doing anything to fix that.
For this to work, you must change the color when binding the view holder every time, not just inside the click listener.
There are two places you need to set the color, both inside and outside the click listener, so you should break the color change out into a function you can reuse.
Also, there is nothing in your click listener that differs based on properties of CalendarModel, so you should move it outside onBind into init so it only has to be created one time. This will help with performance.
inner class CalendarViewHolder(
private val binding: CalendarItemBinding,
containerView: View
) :
RecyclerView.ViewHolder(containerView) {
init {
itemView.setOnClickListener {
clickListener(dateModel)
select = adapterPosition
updateSelectedAppearance()
}
}
fun onBind(dateModel: CalendarModel) {
with(dateModel) {
with(binding) {
updateSelectedAppearance() // called every time we bind
//Show empty days.
txtIranianDate.isVisible = iranianDay != EMPTY_DATE
txtGregorianDate.isVisible = iranianDay != EMPTY_DATE
/** This early return is a code smell. What are you short-circuiting?
You can't short circuit when binding a view holder.
There is no default view state because you might be getting a
recycled view. Anything that you change has to be changed
every time this function is called, even if it is just to change
it back to what was the default state.
if (iranianDay == EMPTY_DATE) {
return
}
*/
// further configuration?
}
}
}
private fun updateSelectedAppearance() {
val isSelected = select == adapterPosition
val color = if (isSelected) R.color.blue else R.color.white
cardDays.setCardBackgroundColor(
ContextCompat.getColor(itemView.context, color)
)
}
}
I found the solution to this issue and from what I thought was very simple, the way was that after clicking, we put the position adapter in the private var select = -1 value and use notifyDataSetChanged() inside the click block and We will change the color of the item outside the click block, pay attention to the code :
class CalendarAdapter(val clickListener: (CalendarModel) -> Unit) :
ListAdapter<CalendarModel, CalendarAdapter.CalendarViewHolder>(CalendarDiffUtils()) {
private lateinit var context: Context
private var selectedItem = -1
//-----
other
//-----
inner class CalendarViewHolder(private val binding: CalendarItemBinding, containerView: View) :
RecyclerView.ViewHolder(containerView) {
fun onBind(dateModel: CalendarModel) {
with(dateModel) {
with(binding) {
txtIranianDate.isVisible = iranianDay != EMPTY_DATE
txtGregorianDate.isVisible = iranianDay != EMPTY_DATE
if (iranianDay == EMPTY_DATE) {
return
}
//Click
itemView.setOnClickListener {
clickListener(dateModel)
selectedItem = adapterPosition
notifyDataSetChanged()
}
//Change color item clicked <-- Do this operation outside the click block
if (selectedItem == adapterPosition) {
cardDays.setBackgroundResource(R.drawable.bg_click_item_calendar)
} else {
cardDays.setBackgroundResource(R.drawable.background_item_calendar)
}
//other-------
}

Jetpack Compose not updating / recomposing Flow List Values from Room DB when DB Data is getting changed

I'm trying to show a List of Items in my Android App. I'm using Jetpack Compose, Flows and RoomDB.
When launching the Activity all Items are shown without any problems, the Flow get's items collected and they are displayed.
But when I change some properties of the Item in the Database, the changes are not displayed. In my case I change the item to deleted, but it's still displayed as not deleted.
When I look at the Database Inspector, the value is changed in the database and set to deleted.
When I log collecting the flow, the change is getting emitted (It says the Item is set to deleted)
But Jetpack Compose is not recomposing the change.
If I delete an element from / add an element to the List (in the DB) the UI gets updated and recomposed.
So I can only assume that the problem must lie in the recomposition or handling of the flow.
Here my Code:
My Activity:
#AndroidEntryPoint
class StockTakingHistoryActivity : ComponentActivity() {
private val viewModel: StockTakingHistoryViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.stockList = ...
setContent {
LaunchedEffect(Unit) {
viewModel.getStockListItems(viewModel.stockList!!.uuid)
}
Surface(color = MaterialTheme.colors.background) {
Content(viewModel.stockListItems)
}
}
}
}
...
#Composable
private fun Content(items: List<StockListItem>) {
...
LazyColumn {
items(items) { item ->
HistoryItem(stockListItem = item)
}
}
}
}
...
#Composable
private fun HistoryItem(stockListItem: StockListItem) {
...
Text(text = stockListItem.deleted)
...
Button(onClick = {
viewModel.deleteItem(stockListItem)
}) {
Text(text = "Set to deleted!")
}
}
}
My ViewModel:
var stockListItems by mutableStateOf(emptyList<StockListItem>())
fun getStockListItems(uuid: String) {
viewModelScope.launch {
stockListItemRepository.findByUUID(uuid).collect { items ->
Log.d("StockTakingHistoryViewModel", "items changed! ${items.map { it.deleted }}")
stockListItems = items
}
}
}
fun deleteItem(stockListItem: StockListItem) {
viewModelScope.launch(Dispatchers.IO) {
stockListItemRepo.update(item.copy(deleted = true);
}
}
The Repository:
fun findByUUID(uuid: String): Flow<List<StockListItem>> {
return dao.findByUUID(uuid)
}
The Dao behind the Repository Request:
#Query("select * from StockListItem where stockListUUID = :uuid order by createdAt desc limit 30")
fun findByUUID(uuid: String): Flow<List<StockListItem>>
I would be very happy if someone could help me! Thank you!
Considering you can collect a flow as state (via collectAsState) I'd consider going that route for getting the list rather than calling collect in the viewModel and updating the stockListItems as there are fewer moving parts for things to go wrong.
For example something like the following:
setContent {
val stockListItems = viewModel.getStockListItemsFlow(uuid).collectAsState(initial = emptyList())
Surface(color = MaterialTheme.colors.background) {
Content(stockListItems)
}
}
Found the Problem: The equals() method of StockListItem only compared the primary key.

Why is data being shown when screen rotates in jetpack compose

I'm facing this issue where the data I'm retrieving from an API, https://randomuser.me/api/ at first compose it doesn't load.
But every time I rotate the screen the data updates.
First load
After screen rotation
View
class MainActivity : ComponentActivity() {
private val userViewModel : UserViewModel by viewModels()
private var userList: List<UserModel> = listOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
userViewModel.userModel.observe(this, Observer {
userList = it
})
userViewModel.onCreate()
setContent {
ListUsers(userList = userList)
}
}
}
ViewModel
class UserViewModel : ViewModel() {
val userModel = MutableLiveData<List<UserModel>>()
var getRandomUsersUseCase = RandomUsersUseCase()
fun onCreate() {
viewModelScope.launch {
val result = getRandomUsersUseCase()
if(!result.isNullOrEmpty()){
userModel.postValue(result)
}
}
}
}
Use State to ensure the data changes trigger recomposition of the Composable.
If you use another observable type such as LiveData in Compose, you
should convert it to State before reading it in a composable using
a composable extension function like LiveData.observeAsState().
Changes to your code would be,
val userListState by userViewModel.userModel.observeAsState()
setContent {
ListUsers(userList = userListState)
}
Why does it shows the data during rotation?
When rotating the screen or during any other configuration changes, the activity will be recreated.
More info on that here - Docs
In most cases, you would not require data to be changed when the screen rotates.
If you want to persist the data even after screen rotation, move the code inside onCreate() in your UserViewModel to the init block, like this.
init {
getData()
}
fun getData() {
viewModelScope.launch {
val result = getRandomUsersUseCase()
if(!result.isNullOrEmpty()){
userModel.postValue(result)
}
}
}
If you need to refresh the data on any other event like button click, swipe to refresh, etc, just call the getData() again on the event handler.
P.S: Check correct imports are added as required.
import androidx.compose.runtime.setValue
import androidx.compose.runtime.getValue

How to "android media player" (Kotlin)

Here's the situation - I've started studying kotlin and android studio, and now I'm stuck with this.
I have a button (ImageView) that when pressed starts to play an audio file.
class MainActivity : AppCompatActivity() {
private var mp: MediaPlayer? = null
private var bruhSound: MutableList<Int> = mutableListOf(R.raw.bruh)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
supportActionBar?.hide()
bruhBtn.setOnClickListener {
if (mp == null) {
controlSound(bruhSound[0])
bruhBtn.setImageResource(R.drawable.btnpressed)
} else if (mp !== null) {
bruhBtn.setImageResource(R.drawable.btn)
}
}
}
private fun controlSound(id: Int) {
if (mp == null) {
mp = MediaPlayer.create(this, id)
Log.d("MainActivity", "ID: ${mp!!.audioSessionId}")
}
mp?.start()
Log.d("MainActivity", "Duration: ${mp!!.duration / 1000} seconds")
}
Currently when I press "bruhBtn", the picture is changing to "btnpressed" and back again correctly, but it wont change after audio is ended. I want it to reset on the audio finishing. I realize that problem is with my code, I need to change the image when the audio is finished. How would I do this?
Before your line
mp?.start
add a listener
mp?.setOnCompletionListener { //change your button state here }

Avoid fragment recreation when opening from notification navigation component

I want when I click on a notification to open a fragment and not recreate it. I am using navigation component and using NavDeepLinkBuilder
val pendingIntent = NavDeepLinkBuilder(this)
.setComponentName(MainActivity::class.java)
.setGraph(R.navigation.workouts_graph)
.setDestination(R.id.workoutFragment)
.createPendingIntent()
My case is I have a fragment and when you exit the app, there is a notification which when you click on it, it should return you to that same fragment. Problem is every time i click on it it's creating this fragment again, I don't want to be recreated.
I had the same issue. Looks like there is not an option to use the NavDeepLinkBuilder without clearing the stack according to the documentation
I'm not sure the exact nature of your action, but I'll make two assumptions:
You pass the destination id to your MainActivity to navigate.
Your MainActivity is using ViewBinding and has a NavHostFragment
You will have to create the pending intent like:
val intent = Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
putExtra("destination", R.id.workoutFragment)
}
val pendingIntent = PendingIntent.getActivity(this, 0, intent, 0);
And in your MainActivity, you can handle both cases (app was already open, app was not already open)
override fun onStart() {
super.onStart()
// called when application was not open
intent?.let { processIntent(it) }
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
// called when application was open
intent?.let { processIntent(it) }
}
private fun processIntent(intent: Intent) {
intent.extras?.getInt("destination")?.let {
intent.removeExtra("destination")
binding.navHostFragment.findNavController().navigate(it)
}
}