Android kotlin - BottomNavigationView use BadgeView inside TimerTask - java

This is the code which works:
class Stackoverflow : AppCompatActivity() {
private var menuView: BottomNavigationMenuView? = null
private var mBottomNavigationView: BottomNavigationView? = null
private var newmsgTimer: Timer? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_home)
mBottomNavigationView = findViewById<View>(R.id.bottom_navigation) as BottomNavigationView
menuView = mBottomNavigationView!!.getChildAt(0) as BottomNavigationMenuView
mBottomNavigationView!!.setOnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.nav_voting -> {
}
R.id.nav_search -> {
}
}
return#setOnNavigationItemSelectedListener true
}
newmsg()
}
private fun newmsg(){
QBadgeView(this#Stackoverflow).bindTarget(menuView!!.getChildAt(3)).badgeNumber = 7 // IT WORKS HERE
newmsgTimer = Timer()
val timerTask = object:TimerTask() {
override fun run() {
Log.d("letsSee", "PIKABOO") // it's getting printed
}
}
newmsgTimer!!.schedule(timerTask, 0, 60000)
}
}
But when I try to use QBadgeView(this#Home).bindTarget(menuView!!.getChildAt(3)).badgeNumber = 7 inside TimerTask:
private fun newmsg(){
newmsgTimer = Timer()
val timerTask = object:TimerTask() {
override fun run() {
Log.d("letsSee", "PIKABOO") // it's getting printed
QBadgeView(this#Stackoverflow).bindTarget(menuView!!.getChildAt(3)).badgeNumber = 7 // DOESN'T WORKS HERE
}
}
newmsgTimer!!.schedule(timerTask, 0, 60000)
}
then it doesn't work.
And I don't know what this error is about:
java.lang.NullPointerException: Attempt to invoke virtual method 'int android.view.View.getVisibility()' on a null object reference
at android.support.design.internal.BottomNavigationMenuView.onMeasure(BottomNavigationMenuView.java:145)
Any ideas how to make it work inside TimeTask? Thanks in advance
EDIT:
After another run I also got this error:
FATAL EXCEPTION: Timer-1
Process: com.exmpl.exmpl, PID: 9978
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
Ignore this: need to add more words blabla

You can not change view properties on another thread directly, you can only change on main thread.
val timerTask = object : TimerTask() {
override fun run() {
Log.d("letsSee", "PIKABOO") // it's getting printed
menuView.post{
QBadgeView(this#Stackoverflow).bindTarget(menuView!!.getChildAt(3)).badgeNumber = 7
}
}

Related

Keep App Android Running after minimize / block phone [duplicate]

This question already has answers here:
Android - implementing startForeground for a service?
(11 answers)
Closed 11 months ago.
I've createde a simple app (following this link How to Play Sound On Button Click in Android Studio Java 2021 ), and everything works fine, but, i need this app can still playing the sound after minimize then or block the phone (is a white noise app from help on sleep for my baby daughter, and i need that keep playing all night).
Is there any way or setting that allows the sound not to stop after minimizing or blocking the application?
Thank you for enveryone that could help me and my baby :)
If you add the ability to run your application in the background, it will solve your problem. Resources you can review:
https://developer.android.com/guide/components/services
https://developer.android.com/guide/background/threading
Basic Example :
AndroidManifest.xml :
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
*
*
*
*
<service android:name=".ui.main.AudioPlayerService"/>
</application>
AudioPlayerService :
private const val PLAYBACK_CHANNEL_ID = "blabla"
private const val PLAYBACK_NOTIFICATION_ID = 1
class AudioPlayerService : Service() {
private var player: SimpleExoPlayer? = null
private var playerNotificationManager: PlayerNotificationManager? = null
private val mediaItem: MediaItem = MediaItem.fromUri(ApiInterface.Channel24LiveURL)
/** Classes to be connected to the service with the
service
*reference providing the link between
*/
private val mBinder = AudioServiceBinder()
/**
* Data that the service will share to other classes. */
inner class AudioServiceBinder : Binder() {
val service
get() = this#AudioPlayerService
val player
get() = this#AudioPlayerService.player
}
override fun onBind(intent: Intent?): IBinder? {
return mBinder
}
/**
* Service start part
*/
override fun onCreate() {
super.onCreate()
// init player
player = SimpleExoPlayer.Builder(this)
.build().apply {
setMediaItem(mediaItem)
playWhenReady = true
prepare()
}
playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(
applicationContext,
PLAYBACK_CHANNEL_ID,
R.string.playback_channel_name,
R.string.playback_channel_desc,
PLAYBACK_NOTIFICATION_ID,
object : PlayerNotificationManager.MediaDescriptionAdapter {
override fun getCurrentContentTitle(player: Player): CharSequence {
return getString(R.string.def_playback_title)
}
override fun createCurrentContentIntent(player: Player): PendingIntent? {
return PendingIntent.getActivity(
applicationContext,
0,
Intent(applicationContext, MainActivity::class.java),
PendingIntent.FLAG_CANCEL_CURRENT
)
}
override fun getCurrentContentText(player: Player): CharSequence? {
return null
}
override fun getCurrentLargeIcon(
player: Player,
callback: PlayerNotificationManager.BitmapCallback
): Bitmap? {
return getBitmapFromVectorDrawable(applicationContext, R.mipmap.ic_launcher)
}
}, object : PlayerNotificationManager.NotificationListener {
override fun onNotificationCancelled(notificationId: Int, dismissedByUser: Boolean) {
stopSelf()
}
override fun onNotificationPosted(notificationId: Int, notification: Notification, ongoing: Boolean) {
if (ongoing) {
// Make sure the service will not get destroyed while playing media.
startForeground(notificationId, notification)
} else {
// Make notification cancellable.
stopForeground(false)
}
}
}
).apply {
// previous and next actions.
setUseNavigationActions(true)
setPlayer(player)
}
}
#MainThread
private fun getBitmapFromVectorDrawable(
context: Context,
#Suppress("SameParameterValue") #DrawableRes drawableId: Int
): Bitmap? {
return ContextCompat.getDrawable(context, drawableId)?.let {
val drawable = DrawableCompat.wrap(it).mutate()
val bitmap = Bitmap.createBitmap(
drawable.intrinsicWidth,
drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
bitmap
}
}
#MainThread
fun changePlayerVolume(view: ImageView) {
player?.let {
if (it.volume == 1.0f) {
it.volume = 0.0f
view.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_volume_off))
} else {
it.volume = 1.0f
view.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.ic_volume_up))
}
}
}
#MainThread
fun jumpLiveStream() {
player?.let {
it.setMediaItem(mediaItem)
it.playWhenReady = true
}
}
/**
* Triggered when the app is closed. */
override fun onTaskRemoved(rootIntent: Intent?) {// Stop the service when the user closes the application.l
releasePlayer()
stopSelf()
super.onTaskRemoved(rootIntent)
}
override fun onDestroy() {
releasePlayer()
stopSelf()
super.onDestroy()
}
/**
* delete player and playerNotificationManager */
private fun releasePlayer() {
player?.let {
it.release()
player = null
}
playerNotificationManager?.let {
it.setPlayer(null)
playerNotificationManager = null
}
}
}
Good luck!

Android: Open and read another app location

I need to develop an app which monitors a list of apps and, if the system is rebooted, return to the last open page of the monitored apps. So, say that android was on the youtube app playing lofi hip hop radio - beats to relax/study to. How could my app send android back to this given video? Also, is there a way for me to know where in the app the user is? For example, can I know that the user was on youtube playing lofi and open it back again for them?
I already know how to open another app as well as detect whichever app is currently open, but I need to know the its uri as well. I can use adb and shell script if it is needed.
I have the following code to launch another app:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
val intent = Intent(this, BackAppListenerService::class.java)
startService(intent)
openApp(this, "com.google.android.youtube")
}
/** Open another app.
* #param context current Context, like Activity, App, or Service
* #param packageName the full package name of the app to open
* #return true if likely successful, false if unsuccessful
*/
fun openApp(context: Context, packageName: String?): Boolean {
val manager = context.packageManager
return try {
val i = manager.getLaunchIntentForPackage(packageName!!)
if (i == null) {
println("Activity not found")
return false;
//throw new ActivityNotFoundException();
}
//throw new ActivityNotFoundException();
i.addCategory(Intent.CATEGORY_LAUNCHER)
context.startActivity(i)
true
} catch (e: ActivityNotFoundException) {
println(e)
false
}
}
}
And with this I get the current foreground app
class BackAppListenerService : Service() {
private var isRunning = false
private var lastApp = ""
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
override fun onCreate() {
isRunning = true
Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
//Creating new thread for my service
//Always write your long running tasks in a separate thread, to avoid ANR
Thread(Runnable {
while (true) {
try {
Thread.sleep(10)
} catch (e: Exception) {
}
val currentForegroundApp = getForegroundApp()
val currentApp = currentForegroundApp.first
if (currentApp != lastApp) {
// New app on front
lastApp = currentApp
println("Current App $lastApp")
}
}
}).start()
return START_STICKY
}
// Must Have Usage Access Permission
fun getForegroundApp(): Pair<String, UsageStats> {
var currentApp = "NULL"
var currentAppInfo: UsageStats? = null
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val usm = this.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
val time = System.currentTimeMillis()
val appList =
usm.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - 1000 * 1000, time)
if (appList != null && appList.size > 0) {
val mySortedMap: SortedMap<Long, UsageStats> =
TreeMap<Long, UsageStats>()
for (usageStats in appList) {
mySortedMap.put(usageStats.lastTimeUsed, usageStats)
}
if (mySortedMap != null && !mySortedMap.isEmpty()) {
currentAppInfo = mySortedMap[mySortedMap.lastKey()]!!
currentApp = mySortedMap[mySortedMap.lastKey()]!!.packageName
}
}
} else {
val am = this.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
val tasks = am.runningAppProcesses
currentApp = tasks[0].processName
}
return Pair(currentApp.split(".").last(), currentAppInfo!!)
}
[...]
}
Any help is much appreciated.

Pass string from onLoadFinished method to another activity

I have created a content retriever which calls data from the content provider in another application, I can read the data successfully, but I want to pass the data from the onLoadFinished Method in my content retriever to an activity which should display the data on button press. But I am unsure of how to do it, I tried using a getter and setter, but it does not print anything.
Here are the rows in my content provider:
String[] columns = new String[]{"_id", "item"};
MatrixCursor matrixCursor = new MatrixCursor(columns);
matrixCursor.addRow(new Object[]{1, "FOUND"});
//Test is a string object that gets serialized to Json
matrixCursor.addRow(new Object[]{2, new Gson().toJson(test)});
matrixCursor.setNotificationUri(getContext().getContentResolver(), uri);
return matrixCursor;
I want to get the data from the 2nd row and this is what I have in my onLoadFinished in my Retriever class:
class Retriever : LoaderManager.LoaderCallbacks<Cursor>{
private var dataFromJson = ""
//getters and setters
fun setData(data: String){
this.dataFromJson = data
}
fun getData():String{
return dataFromJson
}
//other Loader method such as onCreateLoader, etc.
override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor?) {
try
{
val success: Boolean
if (data != null && data.getCount() > 0)
{
data.moveToFirst()
val dataString = data.getString(1)
//this works, I am able to find the "found" text at the first row
success = dataString.equals("FOUND", ignoreCase = true)
//move to 2nd row
data.moveToNext()
//get data from 2nd row does not work
val JSonData = data.getString(1)
setData(JSonData)
}
else
{
Timber.d( "onLoadFinished Unknown error occurred")
success = false
}
//custom interface listener
listener.onClassListener(success)
}
catch (e:Exception) {
Timber.d("Error")
}
}
}
And in my activity when I create a Retriever object and call the getData() method, it displays nothing.
class TestActivity{
private lateinit var dataRetriever: Retriever
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test_activity)
//I have a textView called dataTxt
//displays nothing
dataTxt.text = dataRetriever.getData()
}
}
Any help or advice will be highly appreciated
This is because of the loading task is an async operation, when you call the getData() function, the value of dataFromJson is probably still an empty string.
To easily deal with this, you can declare an interface like this:
interface OnDataRetrieveListener{
fun onDataLoad(dataString: String)
}
and then create an instance of it inside the Retriever
private lateinit var listener: OnDataRetrieveListener
fun setListener(listener: OnDataRetrieveListener){
this.listener = listener
}
set the listener in your activity (if anonymously, it's like below):
dataRetriever.setListener(object: Retriever.Listener{
override fun onDataLoad(data: String) {
dataTxt.text = dataRetriever.getData()
}
})
callback the listener when the string is loaded in Retriever:
...
val jsonData = data.getString(1)
listener.onDataLoad(jsonData)
...
then the dataTxt will update

Implementing MVI Architecture in Android without Mosby

I am trying to implement MVI Architecture in Android, but don't want to use Mosby Library. I want to learn the basics first.
I am building a sample app where when I press a button, text in the textview changes(initially the text is something else). Here is the code for MainActivity and MainPresenter.
class MainActivity : AppCompatActivity(), MainContract.View {
lateinit var mPresenter: MainContract.Presenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mPresenter = MainPresenter()
mPresenter.attachPresenter(this)
bind()
}
#SuppressLint("CheckResult")
private fun bind() {
mPresenter.states().subscribe({ state ->
render(state)
}, {
Log.e("error", "Error is: ", it)
it.printStackTrace()
})
mPresenter.addIntents(intents())
}
override fun intents(): Observable<MainIntent> {
return Observable.merge(
initialIntent(),
clickIntent()
)
}
override fun render(state: MainViewState) {
btn_show.isEnabled = state.isEnabledButton
helloWorldTextView.text = state.message
loadingIndicator.visibility = if (state.isLoading) View.VISIBLE else View.GONE
}
private fun initialIntent(): Observable<MainIntent.InitialIntent> = Observable.just(MainIntent.InitialIntent)
private fun clickIntent(): Observable<MainIntent.ClickIntent> {
return btn_show.clicks().map { MainIntent.ClickIntent("Eureka") }
}
}
class MainPresenter : MainContract.Presenter {
private val intentsSubject: PublishSubject<MainIntent> = PublishSubject.create()
override fun states(): Observable<MainViewState> {
return statesObservable
}
private lateinit var view: MainContract.View
override fun attachPresenter(view: MainContract.View) {
this.view = view
}
#SuppressLint("CheckResult")
override fun addIntents(intents: Observable<MainIntent>) {
intents.subscribe(intentsSubject)
}
private val reducer =
BiFunction { previousState: MainViewState, result: MainResult ->
when (result) {
is MainResult.InitialResult.InFlight -> previousState.copy(
isLoading = true,
message = "Initial Result",
isEnabledButton = false
)
is MainResult.InitialResult.Success -> previousState.copy(
isLoading = true,
message = "Initial Success",
isEnabledButton = true
)
is MainResult.InitialResult.Error -> previousState.copy(
isLoading = false,
message = "Error Initially",
isEnabledButton = true
)
is MainResult.ClickedResult.Success -> previousState.copy(
isLoading = false,
message = System.currentTimeMillis().toString(),
isEnabledButton = true
)
is MainResult.ClickedResult.Error -> previousState.copy(
isLoading = false,
message = "Error Clicked",
isEnabledButton = true
)
is MainResult.ClickedResult.InFlight -> previousState.copy(
isLoading = true,
message = "Clicked In Flight",
isEnabledButton = false
)
}
}
private fun actionFromIntent(intent: MainIntent): MainAction {
if (intent is MainIntent.InitialIntent) {
return MainAction.InitialAction
} else if (intent is MainIntent.ClickIntent) {
return MainAction.ClickedAction("Hello")
} else {
return MainAction.InitialAction
}
}
private var actionProcessor: ObservableTransformer<MainAction, MainResult> = ObservableTransformer { actions ->
actions.publish { shared ->
Observable.merge<MainResult>(
shared.ofType(MainAction.InitialAction::class.java).compose(initialActionProcessor),
shared.ofType(MainAction.ClickedAction::class.java).compose(clickActionProcessor)
)
}
}
private val initialActionProcessor =
ObservableTransformer<MainAction.InitialAction, MainResult.InitialResult> { action: Observable<MainAction.InitialAction> ->
action.switchMap {
Observable.just("hello initially")
.map { MainResult.InitialResult.Success(it) }
.cast(MainResult.InitialResult::class.java)
.onErrorReturn { MainResult.InitialResult.Error(it.message!!) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.startWith { MainResult.InitialResult.InFlight }
}
}
private val clickActionProcessor =
ObservableTransformer<MainAction.ClickedAction, MainResult.ClickedResult> { action: Observable<MainAction.ClickedAction> ->
Observable.just("Click").map { message ->
MainResult.ClickedResult.Success(message)
}.cast(MainResult.ClickedResult::class.java)
.onErrorReturn { MainResult.ClickedResult.Error("Error") }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.startWith { MainResult.ClickedResult.InFlight }
}
private val statesObservable: Observable<MainViewState> = compose()
private fun compose(): Observable<MainViewState> {
return intentsSubject
.map {
actionFromIntent(it)
}
.compose(actionProcessor)
.scan(MainViewState.idle(), reducer)
.distinctUntilChanged()
.replay(1)
.autoConnect(0)
}
}
Problem is that only the Inital event is fired and nothing else. The code doesn't respond to clicks, render is called only initially once.
Also, if I remove the startWith{} from the actionProcessors code responds to clicks, but only once. After that, nothing happens.
Does anyone see issue with the code? I have been trying to get my head around this problem for a while now.
My previous reply:
It's not straight answer to your question. But if you implement what's below, you probably won't have the problem you actually asked about and you'll have easier MVI solution.
You probably try to merge https://speakerdeck.com/jakewharton/the-state-of-managing-state-with-rxjava-devoxx-us-2017, http://hannesdorfmann.com/android/mosby3-mvi-1 and https://medium.com/#oldergod/managing-state-with-rxjava-b0798a6c5757 ideas.
Take a look here: https://proandroiddev.com/taming-state-in-android-with-elm-architecture-and-kotlin-part-1-566caae0f706 - it's simpler. Part 1 and 2 should be enough.
I tried the 1st approach and was repulsed by initial complexity. In 2nd approach you don't have Action, Intent, Result, but Msg instead. It's simpler to reason about.
There's also new MVI course - but haven't checked it yet.
Current approach:
I tried mentioned Elm Architecture, but it is not complete. There are at least 2 problems:
Only one request can get through queue at one moment. Some RxJava
should do the trick (groupBy with 2 streams: ui, background
probably).
parallel requests would update the same state, so you should differentiate DataStates inside your UiState. So different state for different part of UI.
Before writing actual fixes we realised, this is not the way to go ATM: announced Composables could do the MVI trick: smooth and precise data transition to specific parts of UI.
Disclaimer: moderator removed my answer which WAS actual answer. Even more, my answer moved to comment is cut down, which makes it look unfinished. That's why this post emerged once again. After you read it dear moderator, you can remove disclaimer, thanks :)

Have some trouble with thread work

My event emmiter class has code:
private val socketListeners: ArrayList<SocketContentListener> = ArrayList()
//add listener here
override fun subscribe(socketListener: SocketContentListener) {
socketListeners.add(socketListener)
}
private fun getSocketConnectListener()
: SocketContentListener {
/**
* Post received messages to listeners via Handler
* because handler helps to set all messages in order on main thread.
*/
return object : SocketContentListener {
override fun onUdpServerListenerCreated(inetAddress: InetAddress?, port: Int) {
val subscribers = ArrayList<SocketContentListener>(socketListeners)
for (listener in subscribers) {
Handler(Looper.getMainLooper()).post({ listener.onUdpServerListenerCreated(inetAddress, port) })
}
}
}
I try to crete Observable:
val udpObservable = Observable.create<Int> { emitter ->
val listener = object : SocketListener() {
override fun onUdpServerListenerCreated(inetAddress: InetAddress, port: Int) {
emitter.onNext(port)
emitter.onComplete()
}
}
//add listener here
socketSource.subscribe(listener)
emitter.setCancellable { socketSource.unSubscribe(listener) }
}.subscribeOn(Schedulers.io())
.doOnNext { Log.d("123-thread", "current is: " + Thread.currentThread().name) }
.onErrorReturn { throw ConnectionException(it) }
.subscribe()
But during test instead of the expected RxCachedThreadScheduler-1 thread work i saw
D/123-thread: current is:-> main
So can You help me? please. Where is my mistake? How do I achieve the desired RxCachedThreadScheduler thread for rx chains?
The code for creating Observable will be executed on the scheduler, there is no implicit context change anywhere.
  Your events arrive from the listener on the main thread. Then you send them to the emitter on the same thread.
So, subscription go on Schedulers io Thread, but emitters go on Main Thread
 
So the solution is to add observerOn(Schedulers.newThread()) after Observable create. Like this
val udpObservable = Observable.create<Int> { emitter ->
val listener = object : SocketListener() {
override fun onUdpServerListenerCreated(inetAddress: InetAddress, port: Int) {
emitter.onNext(port)
emitter.onComplete()
}
}
//add listener here
socketSource.subscribe(listener)
emitter.setCancellable { socketSource.unSubscribe(listener) }
}.subscribeOn(Schedulers.io())
//need add this for work with emmit data on background
.observerOn(Schedulers.newThread())
.doOnNext { Log.d("123-thread", "current is: " + Thread.currentThread().name) }
.onErrorReturn { throw ConnectionException(it) }
.subscribe()

Categories

Resources