Kotlin check connection in BaseActivity - android

I have created function to check internet connection in my BaseActivity file but it does not work (redirect user)
Code
Code is commented for better understanding
BaseActivity.kt
open class BaseActivity: AppCompatActivity() {
lateinit var prefManager: PrefManager
override fun attachBaseContext(newBase: Context) {
// my app languages codes...
super.attachBaseContext(localeUpdatedContext)
// check internet connection
// What I'm trying to do here is to redirect user into specific activity when connection is lost (it would be best if we can run it every minute or so.)
if(!isNetworkAvailable()) {
val intent = Intent(newBase, NoInternetActivity::class.java)
startActivity(intent)
finish()
}
}
// internet connection function
fun isNetworkAvailable() : Boolean{
val conManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val internetInfo =conManager.activeNetworkInfo
return internetInfo!=null && internetInfo.isConnected
}
class ContextUtils(base: Context) : ContextWrapper(base) {
companion object {
// my other codes...
}
}
open fun openDialog() {}
}
Any suggestions?
Update
I've made following changes which results in redirecting user to next activity if connection is lost but as it is working with Task Timer it refreshes next activity constantly I need it to redirect only once
open class BaseActivity: AppCompatActivity() {
override fun attachBaseContext(newBase: Context) {
// my app languages codes...
super.attachBaseContext(localeUpdatedContext)
// check connection every 5 seconds
val timer = Timer()
val MILLISECONDS = 5000 //5 seconds
val timerr = timer.schedule(CheckConnection(this), 0, MILLISECONDS.toLong())
Log.d("CheckConnection", timerr.toString())
}
internal class CheckConnection(private val context: Context) : TimerTask() {
fun isNetworkAvailable(context: Context): Boolean {
val connectivityManager = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
val activeNetworkInfo = connectivityManager.activeNetworkInfo
return activeNetworkInfo != null && activeNetworkInfo.isConnected
}
override fun run() {
if (!isNetworkAvailable(context)) {
Log.d("CheckConnection", "DISCONNECTED")
val intent = Intent(context, NoInternetActivity::class.java)
context.startActivity(intent)
} else {
Log.d("CheckConnection", "CONNECTED")
}
}
}
}
PS: I've tried to use finish() after my intent startActivity but I got Unresolved reference: finish

Related

Callback called twice when I rotate the device but once in application launch

I have a sample in Github (https://github.com/alirezaeiii/Movies) where I have a utility class in order to check internet network connection :
class NetworkUtils(context: Context) : ConnectivityManager.NetworkCallback() {
private val networkLiveData: MutableLiveData<Boolean> = MutableLiveData()
private val connectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
fun getNetworkLiveData(): LiveData<Boolean> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
connectivityManager.registerDefaultNetworkCallback(this)
} else {
val builder = NetworkRequest.Builder()
connectivityManager.registerNetworkCallback(builder.build(), this)
}
var isConnected = false
connectivityManager.allNetworks.forEach { network ->
val networkCapability = connectivityManager.getNetworkCapabilities(network)
networkCapability?.let {
if (it.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
isConnected = true
return#forEach
}
}
}
networkLiveData.postValue(isConnected)
return networkLiveData
}
override fun onAvailable(network: Network) {
networkLiveData.postValue(true)
}
override fun onLost(network: Network) {
networkLiveData.postValue(false)
}
fun unRegister() {
connectivityManager.unregisterNetworkCallback(this)
}
}
In an Activity I observe in onCreate() and unRegisterNetworkCallback in onDestroy() :
override fun onCreate() {
super.onCreate(savedInstanceState)
handleNetwork()
}
override fun onDestroy() {
super.onDestroy()
networkUtils.unRegister()
}
private fun handleNetwork() {
networkUtils.getNetworkLiveData().observe(this) { isConnected: Boolean ->
if (!isConnected) {
...
} else {
Log.d("Test", "Connected")
...
}
}
}
The 1st time that I launch the app "Connected" tag will be called once, but when I rotate the device it will be called twice. Why is that?
I have another sample where I did not use Navigation architecture component in it and it get called once when I rotate :https://github.com/alirezaeiii/TMDb-Paging/blob/master/app/src/main/java/com/sample/android/tmdb/ui/BaseActivity.kt
Provider using hilt :
#Module
#InstallIn(SingletonComponent::class)
class AppUtilsModule {
#Singleton
#Provides
fun provideNetworkUtils(context: Context): NetworkUtils {
return NetworkUtils(context)
}
}
So as this is singleton. After the First creation of the activity and invoking getNetworkLiveData, internal networkLiveData is filled with value.
After rotation, you are using the same NetworkUtils object, that already has value stored in networkLiveData, during registration existing LiveData is returned, but jus before returning it there is code that posts new value to it.
networkLiveData.postValue(isConnected)
return networkLiveData
As postValue is invoked asynchronously in most cases changing of the internal value will be invoked after the return statement.
So at the end we are registering to livaData which has old value, and just a few ms later we receive new value.
There are a few options to avoid that kind of situation:
Post new value only if it's different from the previous one (Still not perfect)
add Transformations.distinctUntilChanged(networkLiveData) when returning live data (Still not perfect)
Rework a bit of utils, and separate logic related to observing live data and log related to receiving updates from ConnectivityManager
Edit:
There is also another problem after registerNetworkCallback, the callback is invoked with the current status. So there again there is an update of the networkLiveData

How to check internet connection with RxJava in Android

In my application I want use RxJava and I want check internet connection.
I write below code, but just check first time when application started!
I want check every time and when internet connection lost or available show user!
My class codes :
class InternetConnection {
#Suppress("DEPRECATION")
private fun isConnectedOld(context: Context): Boolean {
val connManager = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
val networkInfo = connManager.activeNetworkInfo
return networkInfo!!.isConnected
}
#RequiresApi(Build.VERSION_CODES.M)
private fun isConnectedNewApi(context: Context): Boolean {
val cm = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
val capabilities = cm.getNetworkCapabilities(cm.activeNetwork)
return capabilities?.hasCapability(NET_CAPABILITY_INTERNET) == true
}
fun isConnected(context: Context): Observable<Boolean> {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Observable.just(isConnectedNewApi(context))
} else {
Observable.just(isConnectedOld(context))
}
}
}
Activity codes :
class TestCheckNetworkActivity : AppCompatActivity() {
private lateinit var binding: ActivityTestCheckNetworkBinding
private val internet by lazy { InternetConnection() }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityTestCheckNetworkBinding.inflate(layoutInflater)
setContentView(binding.root)
internet.isConnected(this)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
binding.checkNetTxt.text = it.toString()
}
}
}
How can I change my codes for check internet for every time, not just when application created ?

How to get result using registerForActivityResult from within ktor's Routing call running in a non-activity class?

How to get result from another activity (registerForActivity) from with in ktor's Routing API call (eg. /POST) running in a non-activity class?
Background: For an Android app, I run ktor server engine 'netty' in a non-activity class HttpServer.kt. I need to call another app's activity from with in ktor's Routing' POST handler, so I pass 'appCompatActivity' from MainActivity.kt. That's done, just because, I assume, registerForActivityResult() has dependency on UI/life cycle class.
Problem arises when running this as below, as registerForActivityResult() requires to be run earlier (like onCreate() ?), and I don't have such a class in this non-activity class. Moreover, the callback to run when ActivityResult is returned needs to call ktor ApplicationCall's respond which is also a suspend function.
class HttpServer(
private val applicationContext: AppCompatActivity
) {
private val logger = LoggerFactory.getLogger(HttpServer::class.java.simpleName)
private val server = createServer()
private fun ApplicationCall.startSaleActivityForResult() { // <====== *
val activityLauncherCustom =
applicationContext.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK || result.resultCode == Activity.RESULT_CANCELED) {
val transactionResultReturned = result.data
// Handle the returned result properly using transactionResultReturned
GlobalScope.launch {
respond(status = HttpStatusCode.OK, TransactionResponse())
}
}
}
val intent = Intent()
// Ignoring statements to create proper action/data intent
activityLauncherCustom.launch(intent) // <====== *
}
fun start() = server.start()
fun stop() = server.stop(0, 0)
private fun createServer(): NettyApplicationEngine {
return GlobalScope.embeddedServer(Netty) {
install(CallLogging)
install(ContentNegotiation) {
gson {
setPrettyPrinting()
}
}
routing {
route("/") {
post {
call.startSaleActivityForResult() // <====== *
}
}
}
}
}
private fun <TEngine : ApplicationEngine, TConfiguration : ApplicationEngine.Configuration>
CoroutineScope.embeddedServer(
factory: ApplicationEngineFactory<TEngine, TConfiguration>,
module: Application.() -> Unit
): TEngine {
val environment = applicationEngineEnvironment {
this.parentCoroutineContext = coroutineContext + parentCoroutineContext
this.log = logger
this.module(module)
connector {
this.port = 8081
}
}
return embeddedServer(factory, environment)
}
}
Above is what I tried, but gives below error. And I don't have onCreate on this non-activity class.
java.lang.IllegalStateException: LifecycleOwner com.youtap.upti.MainActivity#38dcf06 is attempting to register while current state is RESUMED. LifecycleOwners must call register before they are STARTED.
Any suggestions to resolve this problem would be grateful.
Below same above snippet as a screenshot to display helper text on declaration/param types from Android Studio:
And I invoke this server class from onCreate() of MainActivity:
To solve your problem and to hide the complexity you can create an intermediate class for launching activity and waiting for a result to come:
import kotlinx.coroutines.channels.Channel
class Repository(private val activity: MainActivity) {
private val channel = Channel<Int>(1)
suspend fun get(input: String): Int {
activity.activityLauncher.launch(input)
return channel.receive()
}
suspend fun callback(result: Int) {
channel.send(result)
}
}
You can store a reference to a repository and an activity launcher in the MainActivity class:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
CoroutineScope(Dispatchers.IO).launch {
HttpServer(this#MainActivity).also { it.start() }
}
}
val activityLauncher = registerForActivityResult(MySecondActivityContract()) { result ->
GlobalScope.launch {
repository.callback(result!!)
}
}
val repository = Repository(this)
}
My second activity and a contract looks like the following:
class ChildActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_child)
val result = Intent()
result.putExtra("name", 6666)
result.data = Uri.parse("http://mydata")
setResult(Activity.RESULT_OK, result)
finish()
}
}
class MySecondActivityContract : ActivityResultContract<String, Int?>() {
override fun createIntent(context: Context, input: String?): Intent {
return Intent(context, ChildActivity::class.java)
.putExtra("my_input_key", input)
}
override fun parseResult(resultCode: Int, intent: Intent?): Int? = when {
resultCode != Activity.RESULT_OK -> null
else -> intent?.getIntExtra("name", 42)
}
override fun getSynchronousResult(context: Context, input: String?): SynchronousResult<Int?>? {
return if (input.isNullOrEmpty()) SynchronousResult(42) else null
}
}
The most simplest part is routing handler:
routing {
route("/") {
post {
val result = (applicationContext as MainActivity).repository.get("input")
call.respondText { result.toString() }
}
}
}
This solution works but only one request is processed at the same time and it's not robust because Activity may be destroyed before HTTP server or repository objects.

Proper way to unregister a callback from an Application class

I have implemented a custom Application class in my app which handles updating the app theme before the app start up.
I also registered a network callback to set a variable each time there is a connection change.
My application class is as such:
Application.kt
package com.th3pl4gu3.mes.ui
.....
class MesApplication : Application() {
companion object {
#Volatile
private var INSTANCE: MesApplication? = null
fun getInstance() =
INSTANCE ?: synchronized(this) {
INSTANCE
?: MesApplication().also { INSTANCE = it }
}
}
override fun onCreate() {
super.onCreate()
// Assigns 'this' to the singleton object
INSTANCE = this
// Updates the application's theme
updateAppTheme()
// Start a network callback to monitor internet connection
startNetworkCallback()
}
private fun startNetworkCallback(){
try{
val cm = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val builder = NetworkRequest.Builder()
cm.registerNetworkCallback(builder.build(), object: ConnectivityManager.NetworkCallback(){
override fun onAvailable(network: Network) {
super.onAvailable(network)
Log.v("INTERNET_TEST", "AC: Network Available")
Global.isNetworkConnected = true
}
override fun onLost(network: Network) {
super.onLost(network)
Log.v("INTERNET_TEST", "AC: Network Lost")
Global.isNetworkConnected = false
}
})
Global.isNetworkConnected = false
}catch (e: Exception){
Global.isNetworkConnected = false
}
}
}
However, from the docs, they recommend to unregister this callback but the Application class lifecycle doesn't have any onPause or onDestroy function.
Is there any proper way to unregister this callback to not cause any memory leaks?
Also feel free to suggest any alternatives in case I am coding this wrong
In this case , you can use ActivityLifecycleCallbacks, to detect are any Activity of your is in Foreground?
ActivityLiveCycleListener
class ActivityLiveCycleListener(private val appStateListener: AppStateListener) : Application.ActivityLifecycleCallbacks {
companion object {
var foregroundActivities = 0
}
override fun onActivityPaused(p0: Activity) {
}
override fun onActivityStarted(p0: Activity) {
if(foregroundActivities == 0){
appStateListener.onAppForeGround()
}
foregroundActivities++
}
override fun onActivityDestroyed(p0: Activity) {
}
override fun onActivitySaveInstanceState(p0: Activity, p1: Bundle) {
}
override fun onActivityStopped(p0: Activity) {
foregroundActivities--
if(foregroundActivities == 0){
appStateListener.onAppBackground()
}
}
override fun onActivityCreated(p0: Activity, p1: Bundle?) {
}
override fun onActivityResumed(p0: Activity) {
}
}
And your interface can have two methods to indicate background/foreground state
interface AppStateListener{
fun onAppForeGround()
fun onAppBackground()
}
Now in Application onCreate(), register to ActivityLifeCycleListener
override fun onCreate(){
registerActivityLifecycleCallbacks(ActivityLiveCycleListener(object : AppStateListener{
override fun onAppForeGround() {
//start network listener
}
override fun onAppBackground() {
//remove network listener
}
}))
}

Android - How to pass a function to an Activity?

I am attempting to create a Builder for an activity. The reason is because this activity can be started many different ways. I created a Builder class like this:
class ActivityBuilder {
private var showToolBar = false
private var postExecutable: (() -> Unit)? = null
fun showToolbar(boolean: Boolean) : ActivityBuilder {
this.showToolBar = boolean
return this
}
fun setPostExecutable(function: () -> Unit) : ActivityBuilder {
this.postExecute = function
return this
}
fun start(context: Context){
val intent = Intent(context, Activity::class.java)
context.startActivity(intent)
}
}
The idea is to call something like this and have access to these fields inside of the activity.
ActivityBuilder().showToolbar(false).setPostExecutable { { doSomething() } }.start(this)
I guess I could also use a companion object and that would serve the same purpose.
companion object Builder {
private var showToolBar = false
private var postExecute: (() -> Unit)? = null
fun showToolbar(boolean: Boolean) : Builder {
this.showToolBar = boolean
return this
}
fun setPostExecutable(function: () -> Unit) : Builder {
this.postExecute = function
return this
}
fun start(context: Context){
val intent = Intent(context, AuthActivity::class.java)
context.startActivity(intent)
}
}
The issue is coming mostly from the "postExecutable" field. I need to call the function at a certain point but it is not parcelable, so I cannot pass it through the intent when starting activity.
If anyone has a solution, I appreciate it!
This is one solution I found, may not be the most elegant. I created a broadcast receiver that I start at the same time as my activity using the parent context.
class ActivityBuilder(private val context: Context) {
private var postSuccessExecutable: (() -> Unit)? = null
...
private fun setupReceiver(){
val filter = IntentFilter()
filter.addAction("SUCCESS")
val receiver = object : BroadcastReceiver() {
override fun onReceive(c: Context?, intent: Intent?) {
context.unregisterReceiver(this)
if (intent?.action == "SUCCESS"){
Toast.makeText(context, "Successful", Toast.LENGTH_SHORT).show()
postSuccessExecutable?.invoke()
}
}
}
context.registerReceiver(authReceiver, filter)
}
...
}
When I want to trigger the function, I just send a broadcast:
private fun sendSuccessBroadcast(data: String){
val intent = Intent()
intent.action = "SUCCESS"
intent.putExtra("data", String)
requireContext().sendBroadcast(intent)
}

Categories

Resources