I have a button in my MapsActivity that communicates to the Wear app. The following Thread is executed:
class NewThread extends Thread {
String path;
String message;
NewThread(String p, String m) {
path = p;
message = m;
}
public void run() {
Task<List<Node>> wearableList =
Wearable.getNodeClient(getApplicationContext()).getConnectedNodes();
try {
List<Node> nodes = Tasks.await(wearableList);
for (Node node : nodes) {
Task<Integer> sendMessageTask =
Wearable.getMessageClient(MapsActivity.this).sendMessage(node.getId(), path, message.getBytes());
try {
Integer result = Tasks.await(sendMessageTask);
sendmessage("I just sent the wearable a message " + sentMessageNumber++);
} catch (final ExecutionException exception) {
MapsActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
Toasty.error(MapsActivity.this, "[1] Something went wrong. Error details: " + exception.getMessage()).show();
}
});
} catch (final InterruptedException exception) {
MapsActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
Toasty.error(MapsActivity.this, "[2] Something went wrong. Error details: " + exception.getMessage()).show();
}
});
}
}
} catch (final ExecutionException exception) {
MapsActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
Toasty.error(MapsActivity.this, "[3] Something went wrong. Error details: " + exception.getMessage()).show();
}
});
} catch (final InterruptedException exception) {
MapsActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
Toasty.error(MapsActivity.this, "[4] Something went wrong. Error details: " + exception.getMessage()).show();
}
});
}
}
}
But I get the following error in the image when trying to accomplish the Thread.
This happens on an Emulator where the WearOS app/api is not available to me (Probably on real devices which do not have WearOS App/Api too).
It happens on the Wearable.getMessageClient(MapsActivity.this).sendMessage(node.getId(), path, message.getBytes());.
I didn't find a clean way of checking the Wear API to be available so I am trying to get the capabilities first, catching the ApiException and setting a isWearableAvailable property:
private fun checkCapability() {
try {
val capability = capabilityClient
.getCapability(CAPABILITY_XYZ, CapabilityClient.FILTER_REACHABLE)
.await()
isConnected = capability.nodes.any(Node::isNearby)
if (BuildConfig.DEBUG) Log.d(LOG_TAG,
"Capability: ${capability.nodes.joinToString()}} > ${isConnected}")
isWearableApiAvailable = true
} catch (e: ApiException) {
isWearableApiAvailable = false
}
}
I'm using the following:
GoogleApiClient mApiClient = new GoogleApiClient.Builder(this)
.addApi( Wearable.API )
...
Since Wearable.API is deprecated? What is the appropriate replacement?
I found something nice which is helpful
private class StartWearableActivityTask extends AsyncTask<Void, Void, Void> {
final String key;
public StartWearableActivityTask(String msg){
key = msg;
}
#Override
protected Void doInBackground(Void... args) {
Collection<String> nodes = getNodes();
for (String node : nodes) {
sendStartActivityMessage(node,key);
}
return null;
}
}
#WorkerThread
private Collection<String> getNodes() {
HashSet<String> results = new HashSet<>();
Task<List<Node>> nodeListTask =
Wearable.getNodeClient(getApplicationContext()).getConnectedNodes();
try {
// Block on a task and get the result synchronously (because this is on a background
// thread).
List<Node> nodes = Tasks.await(nodeListTask);
for (Node node : nodes) {
results.add(node.getId());
}
} catch (ExecutionException exception) {
Log.e(TAG, "Task failed: " + exception);
} catch (InterruptedException exception) {
Log.e(TAG, "Interrupt occurred: " + exception);
}
return results;
}
#WorkerThread
private void sendStartActivityMessage(String node,String event) {
Task<Integer> sendMessageTask =
Wearable.getMessageClient(this).sendMessage(node, event, new byte[0]);
try {
// Block on a task and get the result synchronously (because this is on a background
// thread).
Integer result = Tasks.await(sendMessageTask);
} catch (ExecutionException exception) {
Log.e(TAG, "Task failed: " + exception);
} catch (InterruptedException exception) {
Log.e(TAG, "Interrupt occurred: " + exception);
}
}
I found answer here:
https://developer.android.com/training/wearables/data-layer/migrate-to-googleapi
Migrate Wear apps to GoogleApi
Starting with version 11.8.0 of Google Play services, Wear OS apps should migrate away from the GoogleApiClient class and instead use client objects that are based on the GoogleApi class.
Use of GoogleApi makes it easier to set up asynchronous operations. For example, as described in the introduction to the Tasks API, you can obtain a Task object instead of a PendingResult object.
I am using AsyncTask to get the data form a web service that I created. Now I want to display proper error messages to user like if the Internet is not available it will display toast for that similarly if server is down it will display toast for that. I want to set a string with error like "server is down" or "internet problem occured" in doInBackground() and display Toast in onPostExecute() but I want to know if my server is down that what exception is thrown? and if my server is active but during transfer internet is disconnected so what exception is thrown ?
Probably modelling the response is the best and easiest way.
For example,
you can compose a model from the data you got such as:
class ApiResponse {
public final String responseString;
public final Throwable error;
public ApiResponse (String responseString,Throwable error){
this.responseString = responseString;
this.error = error;
}
}
Then, you can bind response or error to that model and return from doInBackground();
Pseudo code :
class ApiAsyncTask extends AsyncTask<Void, Void, ApiResponse> {
....
protected ApiResponse doInBackground() {
try {
//callApi and get response, if success pass null in error
return new ApiResponse(responseString, null)
} catch (Exception e) {
e.printStackTrace();
//otherwise pass that error
return new ApiResponse(null, e);
}
}
protected void onPostExecute(ApiResponse apiResponse) {
//now just need to check if error is null
if (error == null) {
String json = apiResponse.responseString;
//parse response
} else {
//get error and check with instanceOf
Throwable error = apiResponse.error;
if (error instanceOf SocketTimeoutException){
//show timeout error
}
else if (error instanceOf SomeXYZException){
//handle that exception
}
}
}
}
This is just an example. You can put anything you want in ApiResponse and compose model of that data. (such as status code got from api, generated pojo class of json response by some converter etc. anything). Once you have the data bounded, you can use it in onPostExecute() as it will always be running on your UI thread. Note that Third type param Result of AsyncTask is built for that by definition : AsyncTask<Params, Progress, Result>.
Catch exceptions of your doInBackground() to string and than, depends what you need, you can show toast message from onPostExecute() method, something like this:
#Override
protected Void doInBackground(Void... arg0) {
try {
// do you stuff here
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
response = "UnknownHostException: " + e.toString();
} catch (IOException e) {
e.printStackTrace();
response = "IOException: " + e.toString();
} finally {
...
} catch (IOException e) {
e.printStackTrace();
}
}
#Override
protected void onPostExecute(Void result) {
if (response.contains("IOException: java.net.ConnectException:")) {
Toast.makeText(getApplicationContext(), "YOUR TEXT HERE", Toast.LENGTH_SHORT).show();
mProgressDialog.dismiss();
}
else if (response.contains("IOException: java.net.SocketTimeoutException:")) {
Toast.makeText(getApplicationContext(), "YOUR TEXT HERE", Toast.LENGTH_SHORT).show();
mProgressDialog.dismiss();
}
else if (response.contains("IOException: java.net.SocketException:")) {
Toast.makeText(getApplicationContext(), "YOUR TEXT HERE", Toast.LENGTH_SHORT).show();
mProgressDialog.dismiss();
}
}
Of course, this is yust an example, but you could see how that can be worked out.
I'm using azure sdk for android and follow the tutorial https://azure.microsoft.com/en-us/documentation/articles/mobile-services-dotnet-backend-android-get-started-data/.
When I'm trying to connect and insert data to mobile service table all is ok, but when I query the table in activity my app gets stuck, though there are only several entries in the table and execute method successfully returns Future.
public static MobileServiceClient mClient;
public static void connect(Context context) {
try {
mClient = new MobileServiceClient(storageLink, key, context);
} catch (MalformedURLException e) {
Log.e("AzureService.connect", "Storage access failed" + storageLink);
}
}
public static InstallationData get(final String deviceId) {
MobileServiceTable<InstallationData> table= mClient.getTable(InstallationData.class);
final MobileServiceList<InstallationData> result;
try {
result = table.where().field("deviceid").eq(deviceId).execute().get();
for (InstallationData item : result) {
return item;
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return null;
}
public static void store(final InstallationData item) {
mClient.getTable(InstallationData.class).insert(item, new TableOperationCallback<InstallationData>() {
public void onCompleted(InstallationData entity, Exception exception, ServiceFilterResponse response) {
if (exception == null) {
Log.d("AzureService.store()", "Data about " + item.getDeviceid() + "" + "is successfully updated");
} else {
exception.printStackTrace();
Log.e("AzureService.store()", "Data about " + item.getDeviceid() + "" + "is failed to update");
}
}
});
}
Thank you in advance!
I'm about to do some refactoring off my app and I came to think about this simple yet complex topic, logging, how can it be so hard to do clean, effective and informative logging...
When you read documentation on logging you often see this snippet
if (BuildConfig.DEBUG) {
Log.d(TAG + "message");
}
and it makes me wonder what the purpose is with it? According to the documentation, Android Developer - Log, the debug log messages are compiled in but stripped at runtime, so you wouldn't need to have the log call within that if statement. Or am I missunderstanding anything?
Then I'm also kind of wondering what the real benefit is with using any other Log.x() calls except debug as the log entries will not be seen by the user or logged into some errorfile, so they will be compiled in and executed in the production environment for no purpose at all? This is maybe a use case for the if statement before?
I mentioned earlier that the log entry isn't logged into a file. Why isn't this a built in feature in Android? Is it because of performance issues, unnecessary permission usage or something else? I have implemented this functionality in my own logging class, but now I wonder if it's bad practice? But it's also nice to have logs with important log entries?
So to wrap it up, to implement clean, effective and informative logging, both during development and in production. What's the best practices?
Logs only needed to debug applications during development, to ensure that the feature works as expected and produces desired results. The best practice is, I believe, to do logging in any way which is most convenient to you and allows to find and resolve issues as quickly and efficiently as possible
I mentioned earlier that the log entry isn't logged into a file. Why
isn't this a built in feature in Android?
Who (except a programmer on the development stage) would want an application to waste limited storage space on one's device with useless data? Users don't see, don't read, don't use logs. They don't need this garbage. Application in production must not produce any logs and, of course, must not save them to files.
The only logging which should be implemented in released application is unhandled exceptions logging. Moreover, it's application's responsibility to handle these logs if it suggests sending crash reports, and removing them after the report has been sent.
Another reason logs should not be created by released apps is that they may contain sensitive data and output which requires user authorization, thus introducing security flaws.
I believe the best practice is to remove all logs as soon as the module or feature is fully implemented and thoroughly tested, before deployment to production. Introducing if (BuildConfig.DEBUG) condition helps to ensure this has been achieved.
This will generate clean debug tags with this format ClasssName[MethodName] - LineNumber with reflection.
Complete code with inline comments is available as a gist here.
import android.util.Log;
public class Logger {
public enum LOGGER_DEPTH {
ACTUAL_METHOD(4),
LOGGER_METHOD(3),
STACK_TRACE_METHOD(1),
JVM_METHOD(0);
private final int value;
private LOGGER_DEPTH(final int newValue) {
value = newValue;
}
public int getValue() {
return value;
}
}
private static final String personalTAG = "Logger";
private StringBuilder sb;
private Logger() {
if (LoggerLoader.instance != null) {
Log.e(personalTAG, "Error: Logger already instantiated");
throw new IllegalStateException("Already Instantiated");
} else {
this.sb = new StringBuilder(255);
}
}
public static Logger getLogger() {
return LoggerLoader.instance;
}
private String getTag(LOGGER_DEPTH depth) {
try {
String className = Thread.currentThread().getStackTrace()[depth.getValue()].getClassName();
sb.append(className.substring(className.lastIndexOf(".") + 1));
sb.append("[");
sb.append(Thread.currentThread().getStackTrace()[depth.getValue()].getMethodName());
sb.append("] - ");
sb.append(Thread.currentThread().getStackTrace()[depth.getValue()].getLineNumber());
return sb.toString();
} catch (Exception ex) {
ex.printStackTrace();
Log.d(personalTAG, ex.getMessage());
} finally {
sb.setLength(0);
}
return null;
}
public void d(String msg) {
try {
Log.d(getTag(LOGGER_DEPTH.ACTUAL_METHOD), msg);
} catch (Exception exception) {
Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
}
}
public void d(String msg, LOGGER_DEPTH depth) {
try {
Log.d(getTag(depth), msg);
} catch (Exception exception) {
Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
}
}
public void d(String msg, Throwable t, LOGGER_DEPTH depth) {
try {
Log.d(getTag(depth), msg, t);
} catch (Exception exception) {
Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
}
}
public void e(String msg) {
try {
Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), msg);
} catch (Exception exception) {
Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
}
}
public void e(String msg, LOGGER_DEPTH depth) {
try {
Log.e(getTag(depth), msg);
} catch (Exception exception) {
Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
}
}
public void e(String msg, Throwable t, LOGGER_DEPTH depth) {
try {
Log.e(getTag(depth), msg, t);
} catch (Exception exception) {
Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
}
}
public void w(String msg) {
try {
Log.w(getTag(LOGGER_DEPTH.ACTUAL_METHOD), msg);
} catch (Exception exception) {
Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
}
}
public void w(String msg, LOGGER_DEPTH depth) {
try {
Log.w(getTag(depth), msg);
} catch (Exception exception) {
Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
}
}
public void w(String msg, Throwable t, LOGGER_DEPTH depth) {
try {
Log.w(getTag(depth), msg, t);
} catch (Exception exception) {
Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
}
}
public void v(String msg) {
try {
Log.v(getTag(LOGGER_DEPTH.ACTUAL_METHOD), msg);
} catch (Exception exception) {
Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
}
}
public void v(String msg, LOGGER_DEPTH depth) {
try {
Log.v(getTag(depth), msg);
} catch (Exception exception) {
Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
}
}
public void v(String msg, Throwable t, LOGGER_DEPTH depth) {
try {
Log.v(getTag(depth), msg, t);
} catch (Exception exception) {
Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
}
}
public void i(String msg) {
try {
Log.i(getTag(LOGGER_DEPTH.ACTUAL_METHOD), msg);
} catch (Exception exception) {
Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
}
}
public void i(String msg, LOGGER_DEPTH depth) {
try {
Log.i(getTag(depth), msg);
} catch (Exception exception) {
Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
}
}
public void i(String msg, Throwable t, LOGGER_DEPTH depth) {
try {
Log.i(getTag(depth), msg, t);
} catch (Exception exception) {
Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
}
}
public void wtf(String msg) {
try {
Log.wtf(getTag(LOGGER_DEPTH.ACTUAL_METHOD), msg);
} catch (Exception exception) {
Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
}
}
public void wtf(String msg, LOGGER_DEPTH depth) {
try {
Log.wtf(getTag(depth), msg);
} catch (Exception exception) {
Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
}
}
public void wtf(String msg, Throwable t, LOGGER_DEPTH depth) {
try {
Log.wtf(getTag(depth), msg, t);
} catch (Exception exception) {
Log.e(getTag(LOGGER_DEPTH.ACTUAL_METHOD), "Logger failed, exception: " + exception.getMessage());
}
}
private static class LoggerLoader {
private static final Logger instance = new Logger();
}
}
I've stumbled with that very same problem since I started working in Android and I have created this open source project(Android Studio Macros) that allows you to do what you want plus some more complicated things by using "//<#DEBUG_AREA> and //<#/DEBUG_AREA>" tags within your code, the basic idea is that anything within those tags will be commented when you change your build variants for example if you have something like this in a for loop:
//=========This piece of code is only for logging purposes...=========
Log.e("LogUserInfo", "Name: " + name);
Log.e("LogUserInfo", "Id: " + user.getId());
Log.e("LogUserInfo", "Id: " + user.getDistance());
//====================================================================
In stead of doing this:
if(DEBUG){
Log.e("LogginUserInfo", "Name: " + name);
Log.e("LogginUserInfo", "Id: " + user.getId());
Log.e("LogginUserInfo", "Id: " + user.getDistance());
}
With this macro you can do this(full method):
private List<String> getNamesOfUsersNearMe(String zipCode){
List<User> users = mBusinessLogic.getUsersByZipcode(zipCode);
if(users == null || users.size() < 1){
return null;
}
List<String> names = new ArrayList<String>();
int totalUsers = users.size();
for(int i = 0; i < totalUsers; i++){
User user = users.get(i);
String name = user.getName();
names.add(name);
//<#DEBUG_AREA>
Log.e("LogginUserInfo", "Name: " + name);
Log.e("LogginUserInfo", "Id: " + user.getId());
Log.e("LogginUserInfo", "Id: " + user.getDistance());
//</#DEBUG_AREA>
}
return names;
}
And when you change your build variant to release it would become something like this:
private List<String> getNamesOfUsersNearMe(String zipCode){
List<User> users = mBusinessLogic.getUsersByZipcode(zipCode);
if(users == null || users.size() < 1){
return null;
}
List<String> names = new ArrayList<String>();
int totalUsers = users.size();
for(int i = 0; i < totalUsers; i++){
User user = users.get(i);
String name = user.getName();
names.add(name);
/*<#DEBUG_OFF>
Log.e("LogginUserInfo", "Name: " + name);
Log.e("LogginUserInfo", "Id: " + user.getId());
Log.e("LogginUserInfo", "Id: " + user.getDistance());
</#DEBUG_OFF>*/
}
return names;
}
Which is way better in performance for long loops and makes your code cleaner by getting rid of the unnecessary code while in "release" mode, of course if you go back to "debug" it will uncomment the area and leave it the way it originally was with the "<#DEBUG_AREA>" tags...
Also trying to fit the most common scenarios, seems like there's times when you don't need a full area to get rid of but instead only one single Log, so for that case the project also has a Log wrapper class that you can use as follows:
if(users == null || users.size() < 1){
ASDebuggerMacroLog.e("LogUserInfo", "There's no users available near me...");
return null;
}
The line of code used by the class "ASDebuggerMacroLog" will be commented after changing to "release" mode in your Android Studio.
Hope it Helps!
Regards!
Standard outputs should not be used directly to log anything (squid:S106)
When logging a message there are several important requirements which must be fulfilled:
The user must be able to easily retrieve the logs
The format of all logged message must be uniform to allow the user to easily read
the log
Logged data must actually be recorded
Sensitive data must only be logged securely
If a program directly writes to the standard outputs, there is absolutely no way to comply with those requirements. That's why defining and using a dedicated logger is highly recommended.
Source: Sonarcloud
I highly recommend using Timber library: https://github.com/JakeWharton/timber
It's a very small library on top of Android Log class which takes care of all logging requirements easily. some features:
It automatically figures out which class is being called and use its name as TAG
You can plant different Tree for each build type
All logs going through a central place in Tree. so you process them or upload them somewhere if needed.