Accessing AlarmManager from inside a RecyclerView Adapter - kotlin

I'm making an Android alarm app in class. The alarms are displayed inside a recyclerview in the main activity, and I want them to be deleted when pressed. I am able to clear it from the alarm database I set up but I cannot access AlarmManager to cancel the alarm, and the PendingIntent's context also appears to be wrong.
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, index: Int) {
val myViewHolder = holder as MyViewHolder
val sdf = SimpleDateFormat("HH:mm EEEE")
myViewHolder.tvAlarmTime.text = sdf.format(alarms[index].milliseconds)
myViewHolder.tvAlarmFrequency.text = alarms[index].frequency
myViewHolder.itemView.setOnClickListener {
launch {
withContext(Dispatchers.IO) {
val db = AlarmDatabase.getDatabase(myViewHolder.tvAlarmTime.context)
db.alarmDao().deleteTriggeredAlarm((alarms[index].id))
}
}
val pi = PendingIntent.getBroadcast(this, (alarms[index].id).toInt(), Intent("alarmTask"), PendingIntent.FLAG_UPDATE_CURRENT)
val alarmMgr = getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmMgr.cancel(pi)
}
}
In the last 3 lines, the context has a type mismatch as this is a MyAdapter type - I'm not sure what I need to put here, something similar to MainActivity.context I would assume
getSystemService also shows a type inference error as a string, and I assume this is causing the type mismatch for context.ALARM_SERVICE as a string rather than the context.
What is the correct context and how can I access the AlarmManager inside the adapter?

You can use the context of your item view:
val context = myViewHolder.itemView.context
val pi = PendingIntent.getBroadcast(context, (alarms[index].id).toInt(), Intent("alarmTask"), PendingIntent.FLAG_UPDATE_CURRENT)
val alarmMgr = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
alarmMgr.cancel(pi)

Related

Can you change the color of a textview in a recyclerview adapter after a certain condition is met in Main Activity?

I have a basic function that displays the elapsed time every time the button is pressed. I cannot get the logic in MainActivity to transfer to the recyclerview adapter. I simply want the text output color to change to red after the time passes 5 seconds. I have tried to research how to do this for the past week and I cannot find the exact answer. I'm hoping someone can help.
I have tried it with and without the boolean in the data class. I wasn't sure if that was required.
Here is my code:
Main Activity:`
class MainActivity : AppCompatActivity() {
var startTime = SystemClock.elapsedRealtime()
var displaySeconds = 0
private lateinit var binding: ActivityMainBinding
private val secondsList = generateSecondsList()
private val secondsAdapter = Adapter(secondsList)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
recyclerView.adapter = secondsAdapter
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.setHasFixedSize(false)
binding.button.setOnClickListener {
getDuration()
addSecondsToRecyclerView()
}
}
fun getDuration(): Int {
val endTime = SystemClock.elapsedRealtime()
val elapsedMilliSeconds: Long = endTime - startTime
val elapsedSeconds = elapsedMilliSeconds / 1000.0
displaySeconds = elapsedSeconds.toInt()
return displaySeconds
}
private fun generateSecondsList(): ArrayList<Seconds> {
return ArrayList()
}
fun addSecondsToRecyclerView() {
val addSeconds =
Seconds(getDuration(), true)
secondsList.add(addSeconds)
secondsAdapter.notifyItemInserted(secondsList.size - 1)
}
}
Adapter:
var adapterSeconds = MainActivity().getDuration()
class Adapter(
private val rvDisplay: MutableList<Seconds>
) : RecyclerView.Adapter<Adapter.AdapterViewHolder>() {
class AdapterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textView1: TextView = itemView.tv_seconds
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AdapterViewHolder {
val myItemView = LayoutInflater.from(parent.context).inflate(
R.layout.rv_item,
parent, false
)
return AdapterViewHolder(myItemView)
}
override fun onBindViewHolder(holder: Adapter.AdapterViewHolder, position: Int) {
val currentDisplay = rvDisplay[position]
currentDisplay.isRed = adapterSeconds > 5
holder.itemView.apply {
val redColor = ContextCompat.getColor(context, R.color.red).toString()
val blackColor = ContextCompat.getColor(context, R.color.black).toString()
if (currentDisplay.isRed) {
holder.textView1.setTextColor(redColor.toInt())
holder.textView1.text = currentDisplay.rvSeconds.toString()
} else {
holder.textView1.setTextColor(blackColor.toInt())
holder.textView1.text = currentDisplay.rvSeconds.toString()
}
}
}
override fun getItemCount() = rvDisplay.size
}
Data Class:
data class Seconds(
var rvSeconds: Int,
var isRed: Boolean
)
when you call secondsList.add(addSeconds) then the data that is already inside secondsList should be updated too.
you could do something like
private var secondsList = generateSecondsList() // make this var
fun addSecondsToRecyclerView() {
val addSeconds =
Seconds(getDuration(), true)
secondsList.add(addSeconds)
if ( /* TODO check if time has passed */) {
secondsList = secondsList.map { it.isRed = true }
secondsAdapter.rvDisplay = secondsList // TODO also make rvDisplay a var
secondsAdapter.notifyDatasetChanged() // also need to tell rv to redraw the all views
} else {
secondsAdapter.notifyItemInserted(secondsList.size - 1)
}
}
that might work, but to be honest it looks bad... There is already a lot of logic inside Activity. Read about MVVM architecture and LiveData, there should be another class called ViewModel that would keep track of time and the data. Activity should be as simple as possible, because it has lifecycle, so if you rotate the screen, all your state will be lost.
Your code isn't really working because of this:
var adapterSeconds = MainActivity().getDuration()
override fun onBindViewHolder(holder: Adapter.AdapterViewHolder, position: Int) {
...
currentDisplay.isRed = adapterSeconds > 5
...
}
You're only setting adapterSeconds right there, so it never updates as time passes. I assume you want to know the moment 5 seconds has elapsed, and then update the RecyclerView at that moment - in that case you'll need some kind of timer task that will fire after 5 seconds, and can tell the adapter to display things as red. Let's deal with that first:
class Adapter( private val rvDisplay: MutableList ) : RecyclerView.Adapter<Adapter.AdapterViewHolder>() {
private var displayRed = false
set(value) {
field = value
// Refresh the display - the ItemChanged methods mean something about the items
// has changed, rather than a structural change in the list
// But you can use notifyDataSetChanged if you want (better to be specific though)
notifyItemRangeChanged(0, itemCount)
}
override fun onBindViewHolder(holder: Adapter.AdapterViewHolder, position: Int) {
if (displayRed) {
// show things as red - you shouldn't need to store that state in the items
// themselves, it's not about them - it's an overall display state, right?
} else {
// display as not red
}
}
So with that setter function, every time you update displayRed it'll refresh the display, which calls onBindViewHolder, which checks displayRed to see how to style things. It's better to put all this internal refreshing stuff inside the adapter - just pass it data and events, let it worry about what needs to happen internally and to the RecyclerView it's managing, y'know?
Now we have a thing we can set to control how the list looks, you just need a timer to change it. Lots of ways to do this - a CountdownTimer, a coroutine, but let's keep things simple for this example and just post a task to the thread's Looper. We can do that through any View instead of creating a Handler:
// in MainActivity
recyclerView.postDelayed({ secondsAdapter.displayRed = true }, 5000)
That's it! Using any view, post a delayed function that tells the adapter to display as red.
It might be more helpful to store that runnable as an object:
private val showRedTask = Runnable { secondsAdapter.displayRed = true }
...
recyclerView.postDelayed(showRedTask, 5000)
because then you can easily cancel it
recyclerView.removeCallbacks(showRedTask)
Hopefully that's enough for you to put some logic together to get what you want. Set displayRed = false to reset the styling, use removeCallbacks to cancel any running task, and postDelayed to start a new countdown. Not the only way to do it, but it's pretty neat!
I finally figured it out using a companion object in Main Activity with a boolean set to false. If the time exceeded 5 seconds, then it set to true.
The adapter was able to recognize the companion object and change the color of seconds to red if they exceeded 5.

Why are these log statements not printing?

I'm building an object detection application (in Kotlin, for Android). The application uses CameraX to build a camera preview and Google ML to provide machine learning expertise. Just for reference; I used this CameraX documentation and this this Google ML Kit documentation.
I'm currently attempting to print Log.d("TAG", "onSuccess" + it.size) to my IDE console in order to determine if .addonSuccessListener is actually running. If it does, it should print something along the lines of onSuccess1. However, this isn't the case. Infact, it isn't even printing the Log statement from the .addOnFailureListener either, which really confuses me as I'm not even entirely sure the objectDetector code is even running. What really puzzles me is that I have relatively completed the same project in Java and have not faced this issue.
I did have someone point out that within my YourImageAnalyzer.kt class, if mediaImage is null, then I won't see anything logging. However, upon my own debugging (this is actually my very first time debugging), I was unable to find out if my first sentence of this paragraph is true or not. I suppose this issue may provide a lead into how I'll resolve this issue, and also learn how to properly debug.
Here is my YourImageAnalyzer.kt class, and I will also add the code for my MainActivity.kt class below as well.
YourImageAnalyzer.kt
private class YourImageAnalyzer : ImageAnalysis.Analyzer {
override fun analyze(imageProxy: ImageProxy) {
val mediaImage = imageProxy.image
if (mediaImage != null) {
val image =
InputImage.fromMediaImage(mediaImage, imageProxy.imageInfo.rotationDegrees)
val localModel = LocalModel.Builder()
.setAssetFilePath("mobilenet_v1_0.75_192_quantized_1_metadata_1.tflite")
.build()
val customObjectDetectorOptions =
CustomObjectDetectorOptions.Builder(localModel)
.setDetectorMode(CustomObjectDetectorOptions.STREAM_MODE)
.enableClassification()
.setClassificationConfidenceThreshold(0.5f)
.setMaxPerObjectLabelCount(3)
.build()
val objectDetector =
ObjectDetection.getClient(customObjectDetectorOptions)
objectDetector //Here is where the issue stems, with the following listeners
.process(image)
.addOnSuccessListener {
Log.i("TAG", "onSuccess" + it.size)
for (detectedObjects in it)
{
val boundingBox = detectedObjects.boundingBox
val trackingId = detectedObjects.trackingId
for (label in detectedObjects.labels) {
val text = label.text
val index = label.index
val confidence = label.confidence
}
}
}
.addOnFailureListener { e -> Log.e("TAG", e.getLocalizedMessage()) }
.addOnCompleteListener { it -> imageProxy.close() }
}
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var cameraProviderFuture: ListenableFuture<ProcessCameraProvider>
override fun onCreate(savedInstanceState: Bundle?) {
cameraProviderFuture = ProcessCameraProvider.getInstance(this)
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
cameraProviderFuture.addListener(Runnable {
val cameraProvider = cameraProviderFuture.get()
bindPreview(cameraProvider)
}, ContextCompat.getMainExecutor(this))
}
fun bindPreview(cameraProvider: ProcessCameraProvider) {
val previewView = findViewById<PreviewView>(R.id.previewView)
var preview : Preview = Preview.Builder()
.build()
var cameraSelector : CameraSelector = CameraSelector.Builder()
.requireLensFacing(CameraSelector.LENS_FACING_BACK)
.build()
preview.setSurfaceProvider(previewView.surfaceProvider)
var camera = cameraProvider.bindToLifecycle(this as LifecycleOwner, cameraSelector, preview)
}
}
You are not binding your ImageAnalysis use case. Something in the line of:
val imageAnalysis = ImageAnalysis.Builder()
.setTargetResolution(Size(1280, 720))
.setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
.setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888)
.build()
and then;
imageAnalysis.setAnalyzer(executor, YourImageAnalyzer())
cameraProvider.bindToLifecycle(this as LifecycleOwner, cameraSelector, imageAnalysis, preview)
Also a suggestion as a bonus:
You should get your LocalModel.Builder() out of analyze as this is called each time an image arrives. You do not need to execute this code piece each time as it will make your analysis slower.
So move this code:
val localModel = LocalModel.Builder()
.setAssetFilePath("mobilenet_v1_0.75_192_quantized_1_metadata_1.tflite")
.build()
to just below of the class private class YourImageAnalyzer : ImageAnalysis.Analyzer {.

Can I get a random user out of the firebase realtime database as chat partner as a second option besides a selected user?

I have a problem with coding a Chat application.
One of the planned features is getting connected with a random user from the database.
Currently I have one button that loads an overview of all users, you can chose one user, and you can chat with that person (message send/receive works, also when i close the app and open it again the messages are still viewable and saved).
For that, I have a NewMessageActivity, which on click on one user leads to the ChatLogActivity and takes the user information with it (toUser and USER_KEY).
Now my plan is to basically skip the NewMessage part and get right into the ChatLogActivity, and assign a random user to that.
I was thinking to just add something like an if/else to my code (so, if toUser is assigned by NewMessageActivity load that, otherwise load random), but I can't get it to work.
My users have each individual uid's, that get created randomly when signing up.
Here's my code:
companion object {
val TAG = "ChatLog"
}
val adapter = GroupAdapter<GroupieViewHolder>()
var toUser: User? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_chat_log)
recyclerview_chat_log.adapter = adapter
keyboardManagement()
toUser = intent.getParcelableExtra<User>(NewMessageActivity.USER_KEY)
supportActionBar?.title = toUser?.username
listenForMessages()
send_button_chat_log.setOnClickListener {
Log.d(TAG, "Attempt to send message")
performSendMessage()
}
}
Here how I usually get the users for the chat, in case that is important:
val ref = FirebaseDatabase.getInstance().getReference("/users")
ref.addListenerForSingleValueEvent(object: ValueEventListener{
override fun onDataChange(p0: DataSnapshot) {
val adapter = GroupAdapter<GroupieViewHolder>()
p0.children.forEach{
Log.d("NewMessage", it.toString())
val user = it.getValue(User::class.java)
if (user != null) {
adapter.add(UserItem(user))
}
}
adapter.setOnItemClickListener{ item, view ->
val userItem = item as UserItem
val intent = Intent(view.context, ChatLogActivity::class.java)
//intent.putExtra(USER_KEY, userItem.user!)
intent.putExtra(USER_KEY, userItem.user)
startActivity(intent)
//Zurück zum Main Menu statt zur User Auswahl
finish()
}
recyclerView_newmessage.adapter = adapter
}
override fun onCancelled(p0: DatabaseError) {
}
})
}
}
class UserItem(val user: User): Item<GroupieViewHolder>() {
//wird aufgerufen für die einzelnen Userobjekte
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.itemView.username_textView_new_message.text = user.username
Picasso.get().load(user.profileImageURL).into(viewHolder.itemView.picture_imageView_new_message)
}
//Einzelne Zeilen gestalten
override fun getLayout(): Int {
return R.layout.user_row_new_messages
}
}
I'm still a beginner, so I'm pretty clueless what to do by now. If you have any ideas and could help me, I'd really appreciate that! :)
Thanks!

Kotlin - How do i set a sharedpreference code so that i open the closed app, it opens the last Activity, where i left it

I made two activities in my application, I want the app to be opened where I left off. In other words, not the default activity but the activity where I was when I last exited the app.
You could set a SplashActivity, where your app start, that will start other activities.
In this SplashActivity, you can set a var lastActivity, that will be a code to keep in which activity you were last time.
You get it with SharedPreference, and then go to the activity.
i.e :
String lastActivity = SharedPreference.getString(...) // I don't really remember the syntax
if (lastActivity == "HelloWorldActivity")
startActivity(HelloWorldActivity.getStartIntent(context))
else if (lastActivity == "GoodByeActivity")
startActivity(GoodByeActivity.getStartIntent(context))
Then, do NOT forget to edit your SharedPreference value EACH TIME you change activity.
I don't know if this is a good practice, but feel free to test this and give your think.
EDIT
First, you need to understand how is Shared Preference File. I think it looks like this :
"app_name"="Your app name"
"last_activity"="Your last activity"
"user_age"="23"
This could be the first activity you launch :
class SplashActivity : AppCompatActivity() {
var lastActivity = ""
override fun onCreate(savedInstanceState : Bundle?) {
super.onCreate()
/*
Here, we will get the SharedPreferencesFile.
Then, we get the value linked to the key TAG_LAST_ACTIVITY (set in companion object)
*/
val sharedPref = this.getSharedPreferences(getString(R.string.shared_preference_file_name), 0)
lastActivity = sharedPref.getString(TAG_LAST_ACTIVITY, "")
var activityToStart : AppCompatActivity? = null
if (lastActivity.isBlank())
activityToStart = YourActivityToStartAtFirstLaunch.getStartIntent(this)
else if (lastActivity.equals(TAG_ACTIVITY_ONE))
activityToStart = ActivityOne.getStartIntent(this)
else if (lastActivity.equals(TAG_ACTIVITY_TWO))
activityToStart = ActivityTwo.getStartIntent(this)
else if
... // Use as many as else if you need, but think about the "when" condition, it is better !
startActivity(activityToStart)
}
companion object {
private const val TAG_LAST_ACTIVITY = "last_activity"
private const val TAG_ACTIVITY_ONE = "activity_one"
private const val TAG_ACTIVITY_TWO = "activity_two"
}
}
And this could be your ActivityOne, for example :
class ActivityOne : AppCompatActivity() {
override fun onCreate(savedInstanceState : Bundle?) {
super.onCreate()
/*
Here, we will modify the variable LAST_ACTIVITY in the shared preferences file by setting it to "activity_one".
So, if the user quit this app now, you will know at next launch in which activity he stopped.
I think it is a better practice to set this in the onPause() or onStopped() method. Think about it ! ;)
*/
val sharedPrefEditor = this.getSharedPreferences(getString(R.string.shared_preference_file_name, 0)).edit()
sharedPrefEditor.putString(TAG_LAST_ACTIVITY, TAG_ACTIVITY_ONE)
sharedPrefEditor.apply()
}
companion object {
fun getStartIntent(context : Context) : Intent = Intent(context, ActivityOne()::class.java)
private const val TAG_ACTIVITY_ONE = "activity_one"
private const val TAG_LAST_ACTIVITY = "last_activity"
}
}
Do not forget to put your shared preference file name in your values/strings.xml file :
<string name="shared_preference_file_name">com.example.yourappname.sharedpref"</string>

How to pass the values from activity to another activity

As I'm learning Kotlin for Android development, I'm now trying the basic programs like hello world and how to navigate from one activity to another activity, there is no issue with this.
When I move from one activity to another, it works fine, but I do not know how to pass the values between the activities.
I tried to set the values in one activity and retrieved them in another activity it does not work.
Please see the code snippet below
This is my main activity where I take the username and password from edit text and setting to the intent:
class MainActivity : AppCompatActivity() {
val userName = null
val password = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button.setOnClickListener {
val intent = Intent(this#MainActivity,SecondActivity::class.java);
var userName = username.textø
var password = password_field.text
intent.putExtra("Username", userName)
intent.putExtra("Password", password)
startActivity(intent);
}
}
}
This is my second activity where I have to receive values from the main activity
class SecondActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_second)
var strUser: String = intent.getStringExtra("Username")
var strPassword: String = intent.getStringExtra("Password")
user_name.setText("Seelan")
passwor_print.setText("Seelan")
}
}
Please guide me on how to do this, whether I have some other way to do this in Kotlin if not by intent.
Send value from HomeActivity
val intent = Intent(this#HomeActivity,ProfileActivity::class.java)
intent.putExtra("Username","John Doe")
startActivity(intent)
Get values in ProfileActivity
val profileName=intent.getStringExtra("Username")
I'm on mobile, you must test by yourself.
Try to make a CharSequence to a String in MainActivity , you have put a CharSequence rather than a String, for example:
var userName = username.text.toString()
var password = password_field.text.toString()
In Kotlin, you can pass the data simply by using the Intents. You can directly put your data in intent or you can write those data in bundle and send that bundle to another activity using the intent.
val intent = Intent(this#HomeActivity,ProfileActivity::class.java);
intent.putExtra("profileName", "John Doe")
var b = Bundle()
b.putBoolean("isActive", true)
intent.putExtras(b)
startActivity(intent);
You can simply use the intents and bundle to send data from one activity to another activity.
val intent = Intent(this#OneActivity,TwoActivity::class.java);
intent.putExtra("username", userName)
startActivity(intent);
//On Click on Button
var but = findViewById<Button>(R.id.buttionActivity_two) as Button
but.setOnClickListener {
//Define intent
var intent = Intent(applicationContext,MainActivity::class.java)
// Here "first" is key and 123 is value
intent.putExtra("first",123)
startActivity(intent)
}
}
// If Get In Into Other Activity
var Intent1: Intent
Intent1= getIntent()
//Here first is key and 0 is default value
var obj :Int = Intent1.getIntExtra("first",0);
Log.d("mytag","VAlue is==>"+obj)
first you should do this,
var userName = username.text.toString()
var password = password_field.text.toString()
Add Anko dependency.
implementation "org.jetbrains.anko:anko:0.10.4"
information passing inside MainActivity() is like
startActivity<SecondActivity>("Username" to userName,"Password" to password )
get information from SecondActivity() is,
val profileName=intent.getStringExtra("Username")
You can just access the value without using extras or intent. Simply use companion object in MainActivity:
companion object{
val userName: String = String()
val password: String = String()
}
In SecondActivity:
var strUser: String = MainActivity.username
var strPassword: String = MainActivity.password
And you can access the values from multiple activities easily.
Send data
val Name=findViewById<EditText>(R.id.editTextTextPersonName)
val Name2=findViewById<EditText>(R.id.editTextTextPersonName2)
val name=Name.text.toString()
val age=Name2.text.toString()
val intent1=Intent(this,Second::class.java).also {
it.putExtra("Username",name)
it.putExtra("Age",age)
startActivity(it);
}
Receive data
val name=intent.getStringExtra ("Username")
val age = intent.getStringExtra("Age")
val textView5=findViewById<TextView>(R.id.textView).apply {
text= "Name = $name"
}
val textView6=findViewById<TextView>(R.id.textView2).apply {
text= "Age = $age"
}