okhttp-sse in background - android

I'm building a mobile app that is meant to get server sent events in a background service. I can get SSE events when the app is open but when the app closes, I no longer receive SSE events even though they are accepted in a background service. Any solutions?
AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET"/>
<service
android:name=".NotificationService"
android:enabled="true"
android:exported="true"
android:process=":MyApp_Notifications"/>
MainActivity.kt
startService(Intent(applicationContext, NotificationService::class.java))
NotificationService.kt
class NotificationService : Service() {
var notifChannelId = "RD_N_D_C"
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
createNotificationChannel()
val eventSourceListener = object : EventSourceListener() {
override fun onEvent(
eventSource: EventSource,
id: String?,
type: String?,
data: String
) {
super.onEvent(eventSource, id, type, data)
Log.e(TAG, "\nNOTIF\n")
val data = JSONTokener(data).nextValue() as JSONObject
var builder = NotificationCompat.Builder(this#NotificationService, notifChannelId)
.setSmallIcon(R.drawable.notification_no_bg)
.setContentTitle(data.optString("title"))
.setContentText(data.optString("text"))
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setAutoCancel(true)
with(NotificationManagerCompat.from(this#NotificationService)) {
notify(Random.nextInt(100000, 999999), builder.build())
}
}
override fun onClosed(eventSource: EventSource) {
Log.e(TAG, "\nError - Closed\n")
super.onClosed(eventSource)
}
override fun onFailure(eventSource: EventSource, t: Throwable?, response: Response?) {
Log.e(TAG, "\nError - Failure\n")
super.onFailure(eventSource, t, response)
}
}
val client = OkHttpClient.Builder()
.connectTimeout(5, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.MINUTES)
.writeTimeout(10, TimeUnit.MINUTES)
.build()
val request = Request.Builder()
.url("https://random.website.that/sends/sse/events")
.header("Accept", "application/json; q=0.5")
.addHeader("Accept", "text/event-stream")
.build()
EventSources.createFactory(client)
.newEventSource(request = request, listener = eventSourceListener)
client.newCall(request).enqueue(responseCallback = object : Callback {
override fun onFailure(call: Call, e: IOException) {
Log.e(TAG, "\nError - API Failure\n")
}
override fun onResponse(call: Call, response: Response) {}
})
return START_STICKY
}
// The rest is just onBind(), createNotificationChannel() & onTaskRemoved()

I didn't realize that you can't send notifications from a background service. To fix this all you have to do is change it to a foreground service.
Change
startService(Intent(applicationContext, NotificationService::class.java))
To
val notifIntent = Intent(applicationContext, NotificationService::class.java)
applicationContext.startForegroundService(notifIntent)
You will also need to add the foreground service permission to your AndroidManifest.xml file.
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>

Related

Cannot make Post request in Retrofit Android (Kotlin)

I've been developing an Android Q&A app using Jetpack Compose. I've been trying to make Post requests in Retrofit but the data I send isn't on my API website. I've succeeded in making Get requests though. I've read many documents but I cannot find out what is wrong with this code.
This is data class.
data class UsersEntity(
val id: Int? = null,
val name: String? = null,
val uid: String? = null
)
This is Service interface.
interface UserService {
#POST("createusers")
fun createUsers(#Body usersinfo: UsersEntity): Call<Unit>
}
When I click a button, I'd like to send data to the server. I get the log "Hi, good job" but I cannot see the data on my API.
Button(
onClick = {
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val retrofit = Retrofit.Builder()
.baseUrl("https://api.*****.com/")
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
val service: UserService = retrofit.create(UserService::class.java)
val usersInfo = UsersEntity(
3, "Alex", "164E92FC-D37A")
service.createUsers(usersInfo).enqueue(object: Callback<Unit> {
override fun onResponse(call: Call<Unit>, response: Response<Unit>) {
Log.d("Hi", "good job")
}
override fun onFailure(call: Call<Unit>, t: Throwable) {
Log.d("Hi", "error")
}
})
}
I changed the code like this.
Button(
onClick = {
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val retrofit = Retrofit.Builder()
.baseUrl("https://api.*****.com/")
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
thread {
try {
val service: UserService = retrofit.create(UserService::class.java)
val usersInfo = UsersEntity(
3, "Alex", "164E92FC-D37A")
service.createUsers(usersInfo).enqueue(object: Callback<ResponseBody> {
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) {
Log.d("Response", "${response.body()}")
}
override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
Log.d("Hi", "error")
}
})
} catch (e: Exception) {
Log.d("response", "debug $e")
}
}
},
Could someone help me? Thank you.
I think your baseurl shouldn't end with a slash. Try this.
.baseUrl("https://api.*****.com")
And for your interface (also the Call<ResponseBody>):
interface UserService {
#POST("/createusers/")
fun createUsers(#Body usersinfo: UsersEntity): Call<ResponseBody>
}
Got some issues with this in the past so this might help. If not it atleasts cleans the code a bit :p
Also you can use ProxyMan to intercept your request and read what your application is actually sending to the server, might be a issue to find there!
Proxyman.io

What is the simplest way to make a post request in Kotlin for Android app

The question about post requests in android has been asked before, but all the solutions I've tried have not worked properly. On top of that, a lot of them seem to be overly complicated as well. All I wish to do is make a post to a specific sight with a few body parameters. Is there any simple way to do that?
Let me explain my request calling structure using Retrofit.
build.gradle(app)
// Retrofit + GSON
implementation 'com.squareup.okhttp3:logging-interceptor:4.4.0'
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
ApiClient.kt
object ApiClient {
private const val baseUrl = ApiInterface.BASE_URL
private var retrofit: Retrofit? = null
private val dispatcher = Dispatcher()
fun getClient(): Retrofit? {
val logging = HttpLoggingInterceptor()
if (BuildConfig.DEBUG)
logging.level = HttpLoggingInterceptor.Level.BODY
else
logging.level = HttpLoggingInterceptor.Level.NONE
if (retrofit == null) {
retrofit = Retrofit.Builder()
.client(OkHttpClient().newBuilder().readTimeout(120, TimeUnit.SECONDS)
.connectTimeout(120, TimeUnit.SECONDS).retryOnConnectionFailure(false)
.dispatcher(
dispatcher
).addInterceptor(Interceptor { chain: Interceptor.Chain? ->
val newRequest = chain?.request()!!.newBuilder()
return#Interceptor chain.proceed(newRequest.build())
}).addInterceptor(logging).build()
)
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
return retrofit
}
}
ApiClient will be used to initialize Retrofit singleton object, also initialize logging interceptors so you can keep track of the requests and responses in the logcat by using the keyword 'okhttp'.
SingleEnqueueCall.kt
object SingleEnqueueCall {
var retryCount = 0
lateinit var snackbar: Snackbar
fun <T> callRetrofit(
activity: Activity,
call: Call<T>,
apiName: String,
isLoaderShown: Boolean,
apiListener: IGenericCallBack
) {
snackbar = Snackbar.make(
activity.findViewById(android.R.id.content),
Constants.CONST_NO_INTERNET_CONNECTION, Snackbar.LENGTH_INDEFINITE
)
if (isLoaderShown)
activity.showAppLoader()
snackbar.dismiss()
call.enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
hideAppLoader()
if (response.isSuccessful) {
retryCount = 0
apiListener.success(apiName, response.body())
} else {
when {
response.errorBody() != null -> try {
val json = JSONObject(response.errorBody()!!.string())
Log.e("TEGD", "JSON==> " + response.errorBody())
Log.e("TEGD", "Response Code==> " + response.code())
val error = json.get("message") as String
apiListener.failure(apiName, error)
} catch (e: Exception) {
e.printStackTrace()
Log.e("TGED", "JSON==> " + e.message)
Log.e("TGED", "Response Code==> " + response.code())
apiListener.failure(apiName, Constants.CONST_SERVER_NOT_RESPONDING)
}
else -> {
apiListener.failure(apiName, Constants.CONST_SERVER_NOT_RESPONDING)
return
}
}
}
}
override fun onFailure(call: Call<T>, t: Throwable) {
hideAppLoader()
val callBack = this
if (t.message != "Canceled") {
Log.e("TGED", "Fail==> " + t.localizedMessage)
if (t is UnknownHostException || t is IOException) {
snackbar.setAction("Retry") {
snackbar.dismiss()
enqueueWithRetry(activity, call, callBack, isLoaderShown)
}
snackbar.show()
apiListener.failure(apiName, Constants.CONST_NO_INTERNET_CONNECTION)
} else {
retryCount = 0
apiListener.failure(apiName, t.toString())
}
} else {
retryCount = 0
}
}
})
}
fun <T> enqueueWithRetry(
activity: Activity,
call: Call<T>,
callback: Callback<T>,
isLoaderShown: Boolean
) {
activity.showAppLoader()
call.clone().enqueue(callback)
}
}
SingleEnqueueCall will be used for calling the retrofit, it is quite versatile, written with onFailure() functions and by passing Call to it, we can call an API along with ApiName parameter so this function can be used for any possible calls and by ApiName, we can distinguish in the response that which API the result came from.
Constants.kt
object Constants {
const val CONST_NO_INTERNET_CONNECTION = "Please check your internet
connection"
const val CONST_SERVER_NOT_RESPONDING = "Server not responding!
Please try again later"
const val USER_REGISTER = "/api/User/register"
}
ApiInterface.kt
interface ApiInterface {
companion object {
const val BASE_URL = "URL_LINK"
}
#POST(Constants.USER_REGISTER)
fun userRegister(#Body userRegisterRequest: UserRegisterRequest):
Call<UserRegisterResponse>
}
UserRegisterRequest.kt
data class UserRegisterRequest(
val Email: String,
val Password: String
)
UserRegisterResponse.kt
data class UserRegisterResponse(
val Message: String,
val Code: Int
)
IGenericCallBack.kt
interface IGenericCallBack {
fun success(apiName: String, response: Any?)
fun failure(apiName: String, message: String?)
}
MyApplication.kt
class MyApplication : Application() {
companion object {
lateinit var apiService: ApiInterface
}
override fun onCreate() {
super.onCreate()
apiService = ApiClient.getClient()!!.create(ApiInterface::class.java)
}
}
MyApplication is the application class to initialize Retrofit at the launch of the app.
AndroidManifest.xml
android:name=".MyApplication"
You have to write above tag in AndroidManifest inside Application tag.
MainActivity.kt
class MainActivity : AppCompatActivity(), IGenericCallBack {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val call = MyApplication.apiService.userRegister(UserRegisterRequest(email, password))
SingleEnqueueCall.callRetrofit(this, call, Constants.USER_REGISTER, true, this)
}
override fun success(apiName: String, response: Any?) {
val model = response as UserRegisterResponse
}
override fun failure(apiName: String, message: String?) {
if (message != null) {
showToastMessage(message)
}
}
}
Firstly, we create a call object by using the API defined in ApiInterface and passing the parameters (if any). Then using SingleEnqueueCall, we pass the call to the retrofit along with ApiName and the interface listener IGenericCallBack by using this. Remember to implement it to respective activity or fragment as above.
Secondly, you will have the response of the API whether in success() or failure() function overriden by IGenericCallBack
P.S: You can differentiate which API got the response by using the ApiName parameter inside success() function.
override fun success(apiName: String, response: Any?) {
when(ApiName) {
Constants.USER_REGISTER -> {
val model = response as UserRegisterResponse
}
}
}
The whole concept is to focus on reusability, now every API call has to create a call variable by using the API's inside ApiInterface then call that API by SingleEnqueueCall and get the response inside success() or failure() functions.

How to work Rxjava3 with Retrofit foreground service asynchronous

I want to pull asynchronous json data with retrofit and rxjava 3 in the foreground service and show it to the user as a notification, but so far I have not been successful.
#Streaming
#GET("v2/top-headlines")
fun getDayNewsRxJava(
#Query("country") language : String,
#Query("apiKey") key : String
) : Observable<Model1>
val retrofit = Retrofit.Builder()
.baseUrl("https://newsapi.org/")
.addCallAdapterFactory(RxJava3CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build()
fun returnEveryDay() : everydayNews {
return retrofit.create(everydayNews::class.java)
}
newsRetrofit.returnEveryDay().getDayNewsRxJava("language" , "apiKey")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<Model1> {
override fun onSubscribe(d: Disposable?) {
}
override fun onNext(t: Model1?) {
val title = t!!.articles[0].title
Log.d(TAG , "Rx Java Data : ${t?.articles[0].title}")
sendNotification(title)
}
override fun onError(e: Throwable?) {
Log.e(TAG , "Rx Java Error : $e")
}
override fun onComplete() {
Log.d(TAG , "Rx Java Completed")
}
})
I looked at different examples in this way, they did not use a very different structure, but I do not understand why an asynchronous call is not made.
thanks for your help

How can i keep background android services running when app killed or closed

Hello guys
I have News app, I want to push notification every updates.
I use socketIO to make listen from the server,
My problem with Android Services I want to keep Service running while up closed | killed.
I have start my services when app running and I return in onStartCommand START_STICKY_COMPATIBILITY
its work correctly when app close but while of hour services killed by OS but not start again
any help ?
Note: I ignore battery optimize for the app
My Services Class:
class ServicesMain: Service() {
override fun onBind(p0: Intent?): IBinder? {
return null;
}
#SuppressLint("TrulyRandom")
fun handleSSLHandshake() {
try {
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
override fun getAcceptedIssuers(): Array<X509Certificate>? {
return null;
}
override fun checkClientTrusted(certs: Array<X509Certificate>, authType: String) {}
override fun checkServerTrusted(certs: Array<X509Certificate>, authType: String) {}
})
val sc = SSLContext.getInstance("SSL")
sc.init(null, trustAllCerts, SecureRandom())
HttpsURLConnection.setDefaultSSLSocketFactory(sc.socketFactory)
HttpsURLConnection.setDefaultHostnameVerifier { arg0, arg1 -> true }
} catch (ignored: Exception) {
}
}
#RequiresApi(Build.VERSION_CODES.O)
fun startSocket(){
handleSSLHandshake()
runBroadcast()
Log.d("TAG", "BroadcastReceiver: ")
Timer().schedule(object : TimerTask() {
override fun run() {
Log.d("TAG", "run: ${socket!!.connected()} ")
}
}, 0, 10000)
}
private var socket: Socket? = null;
#SuppressLint("RestrictedApi")
#RequiresApi(Build.VERSION_CODES.O)
fun runBroadcast() {
val myHostnameVerifier = HostnameVerifier { _, _ ->
return#HostnameVerifier true
}
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {}
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {}
override fun getAcceptedIssuers(): Array<X509Certificate> {
return arrayOf()
}
})
val sslContext = SSLContext.getInstance("TLS")
sslContext.init(null, trustAllCerts, null)
val okHttpClient = OkHttpClient.Builder()
.hostnameVerifier(myHostnameVerifier)
.sslSocketFactory(sslContext.socketFactory, trustAllCerts[0] as X509TrustManager)
.readTimeout(0, TimeUnit.MILLISECONDS).writeTimeout(0, TimeUnit.MILLISECONDS).build()
val options = IO.Options()
options.transports = arrayOf(Polling.NAME)
options.webSocketFactory = okHttpClient
options.callFactory=okHttpClient
options.secure = true
socket = IO.socket("https://....", options);
socket!!.connect().on("message", Emitter.Listener { args ->
val jsonObject = JSONObject(args[0] as String)
val calendarTime = jsonObject.getLong("starttime") - (Calendar.getInstance().timeInMillis)
println(calendarTime)
val builder = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
val data: Data.Builder = Data.Builder()
data.putStringArray("data", arrayOf<String>(jsonObject.getString("title"), jsonObject.getString("subject"), jsonObject.getString("id")))
val oneTimeWorkRequest = OneTimeWorkRequest.Builder(NotificationEvetns::class.java).setInputData(data.build()).setConstraints(builder.build())
.setInitialDelay(calendarTime, TimeUnit.MILLISECONDS).addTag(jsonObject.getString("id")).build()
this.let { WorkManager.getInstance(it).enqueue(oneTimeWorkRequest) }
})
}
override fun onTaskRemoved(rootIntent: Intent?) {
super.onTaskRemoved(rootIntent)
Log.d("TAG", "onTaskRemoved: ")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d("TAG", "onStartCommand: ")
return START_STICKY_COMPATIBILITY
}
#RequiresApi(Build.VERSION_CODES.O)
override fun onCreate() {
super.onCreate()
startSocket()
}
override fun onDestroy() {
super.onDestroy()
socket?.disconnect()
}
override fun onTrimMemory(level: Int) {
super.onTrimMemory(level)
Log.d("TAG", "onTrimMemory: ")
}
}
You can't, not on modern versions of Android. You can make a foreground service which is the least likely thing to be killed (outside of the foreground app), but you can't count on it not being killed. Instead write your code so that it doesn't need to- mainly by using WorkManager for triggered background work. Unless you're writing a server, in which case I'd say that you should use another OS, Android isn't suitable.
For messages from a server like you mentioned, I'd use FCM push messaging rather than a long lived direct server connection.

Retrofit, Volley and HttpURLConnection different reponse

Making a simple request to the server giving me different response data. Volley and HttpURLConnection is giving me same response which I expected, however, when I request using retrofit its giving me totally different data.
Please bear in mind that it happens only when I use our WIFI and only in Mobile application. When I use browser and make same request I get expected data.
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val alarmManger = getSystemService(Context.ALARM_SERVICE) as AlarmManager
val intent = Intent(this, AlarmReceiver::class.java)
val pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0)
alarmManger.setRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(),
60 * 1000, pendingIntent)
Thread {
var conn = URL("$BASE_URL/home").openConnection() as HttpURLConnection
var inputStream = conn.inputStream
val br = BufferedReader(InputStreamReader(inputStream))
var sB = StringBuilder()
var line = br.readLine();
if (line != null) {
sB.append(line)
}
print(sB.toString())
}.start()
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.build()
val service = retrofit.create(GitHubService::class.java)
service.listRepos().enqueue(object : Callback<ResponseBody> {
override fun onFailure(call: Call<ResponseBody>?, t: Throwable?) {
println(t)
}
override fun onResponse(call: Call<ResponseBody>?, response: Response<ResponseBody>?) {
println(response?.body())
}
})
val queue = Volley.newRequestQueue(this)
val stringRequest = StringRequest(Request.Method.GET, BASE_URL + "/home",
com.android.volley.Response.Listener<String> { response ->
println(response)
},
com.android.volley.Response.ErrorListener { })
queue.add(stringRequest)
}
}
val BASE_URL = "myserviceurl";
interface GitHubService {
#GET("home")
fun listRepos(): Call<ResponseBody>
}

Categories

Resources