Using Retrofit 2 in Intent Service class - android

I am trying to fetch data from server and sync it on the background of app. I did it using AsyncTask in an Intent Service class, but now I want to make network call using Retrofit. So, I fetched data from server using retrofit but while I am saving them on the local database the main thread freezes, only after completion of the process I can do something on the Main thread. Why is this happening?
I tried both synchronus and Asynchronus request of retrofit but the problem remains. This is what I have tried so far..
//calling company api in synchronus way
try {
val responseCompany = apiService.company(page, headers, bodyModel).execute()
Log.e("onResponse", "Company Response Code: ${responseCompany.code()}")
Log.e("onResponse", "Company Response Body: ${Gson().toJson(responseCompany.body())}")
if (responseCompany.isSuccessful) {
val response = responseCompany.body()
//delete company data
if (response?.delete?.data?.size!! > 0) {
for (i in response.delete.data.indices) {
val delete = response.delete.data[i]
Log.e(tag, "Delete Company data $i: ${delete.company_id}")
dbAdapter.open()
dbAdapter.Delete_COMPANY_NAME(delete.company_id)
dbAdapter.close()
}
}
//insert company data
if (response.insert.data.isNotEmpty()) {
for (i in response.insert.data.indices) {
val insert = response.insert.data[i]
Log.e(tag, "Insert company data $i: ${insert.company_id}")
dbAdapter.open()
dbAdapter.Insert_COMPANY_NAME(insert.company_id.toString(), insert.company_name)
dbAdapter.close()
}
}
//update company data
if (response.update.data.isNotEmpty()) {
for (i in response.update.data.indices) {
val update = response.update.data[i]
Log.e(tag, "Update Company data $i: ${update.company_id}")
dbAdapter.open()
dbAdapter.Update_COMPANY_NAME(update.company_id.toString(), update.company_name)
dbAdapter.close()
}
}
val totalPage = largest(response.delete.meta.pagination.total_pages, response.insert.meta.pagination.total_pages, response.update.meta.pagination.total_pages)
if (page < totalPage) {
prefManager.pageNumber = page + 1
bodyModel.date = lastAdUpdate2
bodyModel.limit = 500
companyData(bodyModel)
} else {
prefManager.T_COMPANY_NAME = currentTime
prefManager.PAGE_NUMBER = 1
prefManager.TOTAL_PAGE = 1
prefManager.pageNumber = 1
prefManager.FIRST = "1"
pagenumber = prefManager.PAGE_NUMBER
Handler().postDelayed({
bodyModel.limit = 100
bodyModel.date = lastAdUpdate3
generics(bodyModel)
}, 1000)
}
} else {
prefManager.dbUpdateStatus = false
Log.i("dataNotInsert", "data Not Insert")
}
} catch (e: Exception) {
Log.e("Exception", "Company: ${e.localizedMessage}")
e.printStackTrace()
}
N.B: I made network call (Retrofit request) in an Intent Service class..
Any Kind of help is highly appreciated. Thank you

This problem was actually solved by replacing Intent Service with Work Manager. And Using Kotlin Coroutine
And if you want to persist with Intent Service rather than using WorkManager. Just wrapping your network call with AsyncTask will solve the problem

Related

How should we chain the work request with the input data in android work manager?

I am trying to learn work manager chaining with passing output of one to another
Here are my objectives,
I have two work request WR1 (gets me the url) and WR2 (sends request to the url)
I can't and should not start the WR2 until the WR1 completes.
WR1 is supposed to return a url to which i have to send to WR2 as inputData
I can mostly do this without chaining. But i would to like explore it in the chaining.
Here is my snippet in progress. Please help.
WorkManager mWorkManager = WorkManager.getInstance(this);
//WR1
OneTimeWorkRequest urlRequest = new
OneTimeWorkRequest.Builder(UriResolveWorker.class).build();
//WR2
OneTimeWorkRequest pullRequest = new
OneTimeWorkRequest.Builder(PullReplicator.class).build();
btnStart.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mWorkManager.beginWith(urlRequest)
.then(pullRequest) // I should be able to pass the result of urlRequest.
.enqueue();
}
});
mWorkManager.getWorkInfoByIdLiveData(urlRequest.getId()).observe(this, new
Observer<WorkInfo>() {
#Override
public void onChanged(WorkInfo workInfo) {
if (workInfo != null) {
WorkInfo.State state = workInfo.getState();
// I will get the URL here and i want to pass this to WR2
message = workInfo.getOutputData().getString("work_result");
tvStatus.append("\n"+"state : "+state.toString() + "message : " +message + "\n");
}
}
});
mWorkManager.getWorkInfoByIdLiveData(pullRequest.getId()).observe(this, new Observer<WorkInfo>() {
#Override
public void onChanged(WorkInfo workInfo) {
if (workInfo != null) {
WorkInfo.State state = workInfo.getState();
String count = workInfo.getOutputData().getString("work_result");
tvStatus.append("\n"+"state : "+state.toString() + " No of Documents : " +count + "\n");o
}
}
});
Before returning the Result object in doWork() method of UriResolveWorker class, You can pass the url to Result.success().
First create object of Data.Builder() and then put the url into:
Data.Builder outputDataBuilder = Data.Builder();
outputDataBuilder.putString(KEY_URL_STRING, url.toString());
after that, create Data object with outputDataBuilder:
Data outputData = outputDataBuilder.build();
now you can return the Result with outputData :
return Result.success(outputData);
Workmanager sends the data to pullRequest when the first one has been done.
Please check the state of the WorkRequest before getting the data.
For example:
private final Observer<WorkInfo> urlWorkInfo = workInfo -> {
if (workInfo == null) {
return;
}
WorkInfo.State state = workInfo.getState();
if (state.isFinished()) {
String url = workInfo.getOutputData()
.getString(KEY_URL)
if (url != null) {
Log.d(TAG_MAIN_ACTIVITY, url);
} else {
Log.d(TAG_MAIN_ACTIVITY, "Url not found!");
}
} else if (state == WorkInfo.State.RUNNING) {
Log.d(TAG_MAIN_ACTIVITY, "Associated WorkRequest is being executed");
}
};
I know it's been a while since you asked this question, but maybe it might help someone in the feature:
In WR1 (Worker 1) you will have output with specific key - data that you want in your second worker (WR2):
Result.success(workDataOf(MY_TEST_KEY to someData.toString()))
Now if you want to get that data in WR2 you cannot send it trough setInputData() method when creating your OneTimeWorkRequest and chaining it.
As everything is forwarded from WR1 to WR2, in WR2 you have to fetch that data like:
inputData.getString(WR1.MY_TEST_KEY)
And than use it as you want in your WR2.
Hopefully this will help someone in the future, official documentation can be found here: https://developer.android.com/topic/libraries/architecture/workmanager/how-to/chain-work

doWork() for WorkManager called multiple times for OneTimeWorkRequest

I have just started to explore WorkManager in my app. My app will mostly be offline, so all the data is stored locally using room db. As soon as the device gets connected to a network I want to sync local data to server and then get the latest data and sync local db again. This is my doWork() method implementation -
#NonNull
#Override
public Result doWork() {
Worker.Result[] result = {Worker.Result.retry()};
count = new CountDownLatch(1);
Context context = getApplicationContext();
try {
new NetworkHelper.NetworkBuilder(context)
.setRequestMethod(NetworkHelper.NetworkBuilder.RequestMethod.GET)
.setTag(NetworkHelper.NetworkBuilder.TAG.FETCH)
.setResponseListener((response, requestMethod, isError) -> {
Utils.printError("onResponse " + isError);
if (!isError) {
clearDataAndInsert(String.valueOf(response));
}
})
.build().callFetchData();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
try {
count.await();
} catch (InterruptedException e) {
Utils.printDebug(e.getMessage());
}
Utils.printError(result[0].toString());
return result[0];
}
in clearDataAndInsert() I am inserting the data that was fetched from server to local db using room and for this, I built my own callback listeners and checking if all my data is successfully inserted in the db using atmoic integer like this -
#Override
public void onTaskComplete() {
int remaining = task.decrementAndGet();
if (remaining == 0) {
Data source = new Data.Builder()
.putString("workInfo", "completed")
.build();
result[0] = Worker.Result.success(source);
count.countDown();
}
}
This is how I am enqueing the data -
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresDeviceIdle(false)
.build();
Data source = new Data.Builder()
.putString("workType", "OneTime")
.build();
OneTimeWorkRequest request = new OneTimeWorkRequest.Builder(SyncWorker.class)
.setConstraints(constraints)
.setInputData(source)
.build();
WorkManager.getInstance(context).enqueue(request);
As you can see I am using CountDownLatch to wait for my fetch and insert in local db process to finish and then return success result from doWork(). But my problem is, my doWork gets called multiple times, I am guessing that's because result is returning as retry? But I cannot figure out why?

httpClient.PostAsync crashes app in Xamarin Android JobService

I have an Android Xamarin app that handles notifications. When a notification is displayed, there are buttons that ask for a response. The app needs to send this response back to a server via an httpClient.PostAsync call. I am using the same http client wrapper in other parts of the code and it is working correctly. However, when I call it from the JobService code, the app crashes. I have enclosed the http call in a try/catch and no exception occurs. There are also no errors in the device log. i would like to know how to debug this. Here is my flow:
I have a class that derives from FirebaseMessagingService with an OnMessageReceived method. That gets called when a notification arrives. I build a local notification via the notification manager and call .Notify. The notification appears with the buttons. I have a BroadcastReceiver with an OnReceive method. That method schedules a job to do the post back of the button click. The job gets started and runs until the point I call the PostAsync. From there it crashes with no exception. Here is the relevant part of the JobWorker:
public override bool OnStartJob(JobParameters jobParams)
{
_backgroundWorker = Task.Run(() => { DoWork(jobParams); });
return true;
}
private void DoWork(JobParameters jobParams)
{
var logger = App.ResolveDependency<ILogger>() as ILogger;
var callActions = App.ResolveDependency<ICallActionsHandler>() as ICallActionsHandler;
var callToken = jobParams.Extras.GetString(JobParameterCallToken);
var subsciberPhoneNumber = jobParams.Extras.GetString(JobParameterSubscriberPhoneNumber);
var action = jobParams.Extras.GetString(JobParametersCallAction);
logger.TraceInfo($"starting {nameof(CallActionService)}: starting job {jobParams.JobId}");
callActions.SendAction(
callToken,
subsciberPhoneNumber,
(CallActions)Enum.Parse(typeof(CallActions), action));
}
The SendAction code calls the http client wrapper. The http client wrapper code looks like this:
public async Task<int> PostAsync(string api, object message)
{
var apiUrl = Constants.DefaultAppApi + api;
var contentText = JsonConvert.SerializeObject(message);
var content = new StringContent(contentText, Encoding.UTF8, "application/json");
var backOff = 10;
var retryCount = 5;
HttpResponseMessage response = null;
for (var attempt = 1; attempt <= retryCount; attempt++)
{
_logger.TraceInfo($"DataServerClient Post message: {message.GetType().Name}, attempt = {attempt}");
try
{
response = await _client.PostAsync(apiUrl, content);
}
catch (Exception ex)
{
if (attempt == retryCount)
_logger.TraceException($"DataServerClient Post failed", ex);
}
if (response != null && response.IsSuccessStatusCode)
{
_logger.TraceInfo($"DataServerClient post was successful at retry count: {attempt}");
break;
}
backOff *= 2;
await Task.Delay(backOff);
}
return (int)response.StatusCode;
}
Can anyone provide clues for why this is failing or how I can gather diagnostics to find out what is happening? As I mentioned, the exception is not caught, the task that I create gets marked as completed, and no message gets posted.

Auto send push notification from app server

Is there any way to send a push notification from server when a user complete a task? For example: A todo app will notify on that date with push notification. I want to use firebase and firestore for storing user tokens.
Alarm manager can be a solution that I have found but I don't wanna use it.
Yes, you can use scheduler to send notification from server to your app:
You may follow my working code:
Emplement IJob:
public class SendNotificationViaFcm: IJob
{
public void Execute(IJobExecutionContext context)
{
bool isNotificationSent=false;
try
{
var taskToSendNotification = FirebaseCloudMessaging.SendMessage();
Task.WaitAll(taskToSendNotification);
isNotificationSent = taskToSendNotification.Result;
}
catch (Exception exception)
when (
exception is ObjectDisposedException || exception is ArgumentNullException ||
exception is AggregateException)
{
}
catch (Exception exception) when (exception is InvalidOperationException)
{
}
catch (Exception exception)
{
// ignored
}
}
}
Call FCM Api from your server:
public class FirebaseCloudMessaging
{
private static readonly Uri FcmUri = new Uri(
uriString: #"https://fcm.googleapis.com",
uriKind: UriKind.Absolute);
private const string FcmApiKey = "Your Legacy Server Key";
public static async Task<bool> SendMessage()
{
using (var httpClient = new HttpClient())
{
httpClient.BaseAddress = FcmUri;
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Authorization",
"key=" + FcmApiKey);
var response = await httpClient.PostAsJsonAsync(#"/fcm/send", new
{
to = "/topics/global",
priority = "high",
data = new
{
title = "Warning",
message = "Please start app to track movemoent!"
}
//to = "/topics/global",
//priority = "high",
//notification = new
//{
// title = "Warning!",
// body = "Please start app to track movemoent!"
//}
});
Debug.Write(response.Content.ReadAsStringAsync());
var ck = response.IsSuccessStatusCode;
return response.IsSuccessStatusCode;
}
}
}
Implement schedular for your time interval:
public class Scheduler
{
public static void Start()
{
try
{
IScheduler scheduler = StdSchedulerFactory.GetDefaultScheduler();
scheduler.Start();
// scheduler.Shutdown();
var sentCloudNotification = JobBuilder.Create<SendNotificationViaFcm>().Build();
var cloudNotificationTrigger = TriggerBuilder.Create().WithSimpleSchedule(x => x.WithIntervalInMinutes(1).RepeatForever()).Build();
scheduler.ScheduleJob(sentCloudNotification, cloudNotificationTrigger);
}
catch (SchedulerException exception)
{
Debug.Write(exception.Message);
}
catch (Exception exception)
{
Debug.Write(exception.Message);
}
}
}
Finally Run in from your Global.asax.cs
protected void Application_Start()
{
Scheduler.Start();
}
It sounds like you're looking for a tool that allows you to schedule transactional notifications. What sort of server technology are you using?
From a high level you could do something like this:
1) user adds a task in the Android application
2) android application sends request to server to save the task
3) you have some code that runs in some sort of on task save callback that schedules a block of code to run in the future using crontab, celery or something similar.
4) the block of code that runs in the future is an api call to twilio to send a push notification
relevant links: https://www.twilio.com, https://firebase.google.com/docs/cloud-messaging/, http://www.celeryproject.org/, https://en.wikipedia.org/wiki/Cron

System.Net.Http.HttpClient with AutomaticDecompression and GetAsync (timeout) vs GetStringAsync (working

I have the following code to make requests to a REST API, using Xamarin and an Android device:
public class ApiBase
{
HttpClient m_HttpClient;
public ApiBase(string baseAddress, string username, string password)
{
if (!baseAddress.EndsWith("/"))
{
baseAddress += "/";
}
var handler = new HttpClientHandler();
if (handler.SupportsAutomaticDecompression)
{
handler.AutomaticDecompression = DecompressionMethods.GZip;
}
m_HttpClient = new HttpClient(handler);
m_HttpClient.BaseAddress = new Uri(baseAddress);
var credentialsString = Convert.ToBase64String(Encoding.UTF8.GetBytes(username + ":" + password));
m_HttpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", credentialsString);
m_HttpClient.Timeout = new TimeSpan(0, 0, 30);
}
protected async Task<XElement> HttpGetAsync(string method)
{
try
{
HttpResponseMessage response = await m_HttpClient.GetAsync(method);
if (response.IsSuccessStatusCode)
{
// the request was successful, parse the returned string as xml and return the XElement
var xml = await response.Content.ReadAsAsync<XElement>();
return xml;
}
// the request was not successful -> return null
else
{
return null;
}
}
// some exception occured -> return null
catch (Exception)
{
return null;
}
}
}
If i have it like this, the first and the second call to HttpGetAsync work perfectly, but from the 3rd on the GetAsyncstalls and eventually throws an exception due to the timeout. I send these calls consecutively, there are not 2 of them running simultaneously since the results of the previous call are needed to decide the next call.
I tried using the app Packet Capture to look at the requests and responses to find out if i'm sending an incorrect request. But it looks like the request which fails in the end is never even sent.
Through experimentation i found out that everything works fine if don't set the AutomaticDecompression.
It also works fine if i change the HttpGetAsync method to this:
protected async Task<XElement> HttpGetAsync(string method)
{
try
{
// send the request
var response = await m_HttpClient.GetStringAsync(method);
if (string.IsNullOrEmpty(response))
{
return null;
}
var xml = XElement.Parse(response);
return xml;
}
// some exception occured -> return null
catch (Exception)
{
return null;
}
}
So basically using i'm m_HttpClient.GetStringAsync instead of m_HttpClient.GetAsync and then change the fluff around it to work with the different return type. If i do it like this, everything works without any problems.
Does anyone have an idea why GetAsync doesn't work properly (doesn't seem to send the 3rd request) with AutomaticDecompression, where as GetStringAsync works flawlessly?
There are bug reports about this exact issue:
https://bugzilla.xamarin.com/show_bug.cgi?id=21477
The bug is marked as RESOLVED FIXED and the recomended action is to update to the latest stable build. But there are other (newer) bugreports that indicate the same thing that are still open, ex:
https://bugzilla.xamarin.com/show_bug.cgi?id=34747
I made a workaround by implementing my own HttpHandler like so:
public class DecompressionHttpClientHandler : HttpClientHandler
{
protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.Headers.AcceptEncoding.Add(new System.Net.Http.Headers.StringWithQualityHeaderValue("gzip"));
var msg = await base.SendAsync(request, cancellationToken);
if (msg.Content.Headers.ContentEncoding.Contains("gzip"))
{
var compressedStream = await msg.Content.ReadAsStreamAsync();
var uncompresedStream = new System.IO.Compression.GZipStream(compressedStream, System.IO.Compression.CompressionMode.Decompress);
msg.Content = new StreamContent(uncompresedStream);
}
return msg;
}
}
Note that the code above is just an example and not a final solution. For example the request will not be compressed and all headers will be striped from the result. But you get the idea.

Categories

Resources