Passing a string "dynamically" as a class name in Android Studio - android

Let's say you have a string:
String string_name; //assign whatever value
And you have an intent:
Intent i = new Intent(getApplicationContext(), string_name.class);
This obviously doesn't work. AS doesn't recognise string_name as a class (although IT EXISTS as an activity in the main folder). The forname method didn't work for me either (unless I did it wrong).
I have 10 activities/classes listed name1, name2, name3, etc... And after I'm done with each activity, the program goes to a "Transition" activity page, which then redirects to the next activity at run time. So after the user is done with name1 activity, the program redirects him to the "Transition" page. And after that I'm trying to send them to name2 activity. And so on.
What I'm trying to do is assign the name of name1, name2, activities to a string (string_name in this case) in the "Transition" activity/class. After a couple of lines of code, I managed to retrieve the name of name1, change it to name2, and store it in a string. But Android Studio does not accept a "dynamic" string as a class value.
Thoughts?

Instead of this:
Intent i = new Intent(getApplicationContext(), string_name.class);
You can do this:
Intent i = new Intent();
// Set the component using a String
i.setClassName(getApplicationContext(), string_name);
NOTE: Make sure that all of your activities are declared in your manifest.

Updated:
You can use Class.forName(String string_name) to get class from a String. But in String you have to give complete package name of that class.
String string_name = "com.your_package.TestActivity";
try {
Class<?> classByName = Class.forName(string_name);
Intent i = new Intent(this, classByName.class);
} catch (ClassNotFoundException e) {
Log.e("YourParentClassName", "System can't find class with given name: " + string_name);
}

It looks like this snippet should help you:
String string_name = "com.package.ActivityToStart";
Intent i = null;
try {
i = new Intent(this, (Class<?>) Class.forName(string_name).newInstance());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
Log.e("YourParentClassName", "System can't find class with given name: " + string_name, e);
}
if (i != null) {
// do your work
}

For a string to class name conversion you need to properly give your package name else it will always throw exception
String s = "yourclassname";
try {
context.startActivity(new Intent(context, Class.forName("your.package.name." + s)));
} catch (ClassNotFoundException e) {
Toast.makeText(context, s + " does not exist yet", Toast.LENGTH_SHORT).show();
}

Class.forName() used for creating object of class Class. Below is the syntax :
Class c = Class.forName(String className)
The above statement creates the Class object for the class passed as a String argument(className). Note that the parameter className must be fully qualified name of the desired class for which Class object is to be created. The methods in any class in java which returns the same class object are also known as factory methods. The class name for which Class object is to be created is determined at run-time.

Related

How do I send data from an android wearable device to a phone in the form of a a simple text file containing data?

I have a wearable app. The app after it finishes has data like time/date, UUID, Geo location, parameters selected displayed in front of me like a Data Report or Log in several TextViews underneath each other. Like a list. I want this data to be transferred from my wearable device to my android phone.
Now I have to ask does the WearOS app the pairs the phone with the watch enables such a thing? Like can the data be sent through it? OR what exactly can I do? I read about Sync data items with the Data Layer API in the documentation, but I'm not sure if the code snippets provided would help achieve what I want.
public class MainActivity extends Activity {
private static final String COUNT_KEY = "com.example.key.count";
private DataClient dataClient;
private int count = 0;
...
// Create a data map and put data in it
private void increaseCounter() {
PutDataMapRequest putDataMapReq = PutDataMapRequest.create("/count");
putDataMapReq.getDataMap().putInt(COUNT_KEY, count++);
PutDataRequest putDataReq = putDataMapReq.asPutDataRequest();
Task<DataItem> putDataTask = dataClient.putDataItem(putDataReq);
}
...
}
The data I display in the textviews are called through methods that I call things like: getLocation, getUUID, getDateTime, getSelections, etc... when I click a button I call them in the setOnClickListener. I want this data in the TextViews to be placed in a file or something like that and send them over to the mobile phone from the watch when they're generated.
private void getDateTime()
{
SimpleDateFormat sdf_date = new SimpleDateFormat("dd/MM/yyyy");
SimpleDateFormat sdf_time = new SimpleDateFormat("HH:mm:ss z");
String currentDate= sdf_date.format(new Date());
String currentTime= sdf_time.format(new Date());
textView_date_time.setText("Date: "+currentDate+"\n"+"Time: "+currentTime);
}
#SuppressLint("SetTextI18n")
private void getUUID()
{
// Retrieving the value using its keys the file name
// must be same in both saving and retrieving the data
#SuppressLint("WrongConstant") SharedPreferences sh = getSharedPreferences("UUID_File", MODE_APPEND);
// The value will be default as empty string because for
// the very first time when the app is opened, there is nothing to show
String theUUID = sh.getString(PREF_UNIQUE_ID, uniqueID);
// We can then use the data
textView_UUID.setText("UUID: "+theUUID);
}
#SuppressLint("SetTextI18n")
private void getSelections()
{
textView_data_selected.setText("Tool No.: "+c.getToolNo()+
"\nTool Size: " +c.getToolSizeStr()+
"\nFrom Mode: " +c.getCurrentModeStr()+
"\nGoto Mode: " +c.getModeStr()+
"\nMethod: " +c.getMethodStr()+
"\nBit Duration: " +c.getBitDuration()+
"\nUpper bound" +c.getUpStageValue()+
"\nLower bound: "+c.getDownStageValue());
}
The above are examples of the methods I use to get the data. then I call them here:
gps_btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (Build.VERSION.SDK_INT >= 26) {
getLocation();
getDateTime();
getUUID();
getSelections();
}
else
{
//ActivityCompat.requestPermissions(get_location.this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 1);
Toast.makeText(get_location.this,"Build SDK too low",Toast.LENGTH_SHORT);
}
}
});
Now how do I take all this and send it over from my device to the the phone?
Note: The data report I want to send as a file, I want it done subtly like something done in the background. I don't know what else to do or where to look.
You have two options if you want to use the Data Layer, one is to use the MessageClient API to bundle your data up in a message and send it directly to the handheld. The easiest here would be to create an arbitrary JSONObject and serialize your data as a JSON string you can stuff into a message. For example:
try {
final JSONObject object = new JSONObject();
object.put("heart_rate", (int) event.values[0]);
object.put("timestamp", Instant.now().toString());
new MessageSender("/MessageChannel", object.toString(), getApplicationContext()).start();
} catch (JSONException e) {
Log.e(TAG, "Failed to create JSON object");
}
In my case, I do this in my onSensorChanged implementation, but you can insert this wherever you are updating your text.
MessageSender is just a threaded wrapper around the MessageClient:
import java.util.List;
class MessageSender extends Thread {
private static final String TAG = "MessageSender";
String path;
String message;
Context context;
MessageSender(String path, String message, Context context) {
this.path = path;
this.message = message;
this.context = context;
}
public void run() {
try {
Task<List<Node>> nodeListTask = Wearable.getNodeClient(context.getApplicationContext()).getConnectedNodes();
List<Node> nodes = Tasks.await(nodeListTask);
byte[] payload = message.getBytes();
for (Node node : nodes) {
String nodeId = node.getId();
Task<Integer> sendMessageTask = Wearable.getMessageClient(context).sendMessage(nodeId, this.path, payload);
try {
Tasks.await(sendMessageTask);
} catch (Exception exception) {
// TODO: Implement exception handling
Log.e(TAG, "Exception thrown");
}
}
} catch (Exception exception) {
Log.e(TAG, exception.getMessage());
}
}
}
The other option is to create a nested hierarchy of data items in the Data Layer and implement DataClient.OnDataChangedListener on both sides, such that changes that are written in on one side are automatically synchronized with the other. You can find a good walkthrough on how to do that here.
For your specific case, just packing it in a JSON object would probably be the simplest. The writing out to your preferred file format you can then implement on the handheld side without needing to involve the wear side.

Get human-readable name of default keyboard (not package name)

Is there a possibility in Android (API 24 - 29) to get the human-readable name of the current default keyboard? When I use the following code
String keyboard = Settings.Secure.getString(getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
I'm getting
com.google.android.inputmethod.latin/com.android.inputmethod.latin.LatinIME
But I would like to have Gboard instead (i.e. the name that is displayed in the keyboard selection menu and not the package name).
That result is the String form of a ComponentName, so we can use the unflattenFromString() method to easily parse out the package name, and then retrieve the package's label – i.e., the human-readable name – from its ApplicationInfo obtained with PackageManager. For example, in a simple Java utility method:
public static CharSequence getCurrentImeLabel(Context context) {
CharSequence readableName = null;
String keyboard = Settings.Secure.getString(context.getContentResolver(), DEFAULT_INPUT_METHOD);
ComponentName componentName = ComponentName.unflattenFromString(keyboard);
if (componentName != null) {
String packageName = componentName.getPackageName();
try {
PackageManager packageManager = context.getPackageManager();
ApplicationInfo info = packageManager.getApplicationInfo(packageName, 0);
readableName = info.loadLabel(packageManager);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
return readableName;
}
And an equivalent Kotlin extension on Context:
fun Context.getCurrentImeLabel() : CharSequence? {
val keyboard = Settings.Secure.getString(contentResolver, DEFAULT_INPUT_METHOD)
return ComponentName.unflattenFromString(keyboard)?.let {
packageManager.getApplicationInfo(it.packageName, 0).loadLabel(packageManager)
}
}
If all you need is that human-readable name, then this seems to be the most direct approach, as InputMethodManager doesn't appear to have any public method that returns the current IME.
However, if you need additional IME-specific information, it seems that it will have to be pulled from the List<InputMethodInfo> returned from InputMethodManager's getInputMethodList() or getEnabledInputMethodList() methods. In both cases, you would need to iterate over the List, checking for which InputMethodInfo#getId() equals that String returned from Settings.
The InputMethodInfo class also has a loadLabel(PackageManager) method available, so if you're using this method, or otherwise have the necessary InputMethodInfo already, then you can use that directly, rather than making an unnecessary getApplicationInfo() call.

Android - Simple XML Framework. #Convert interferes with #Attribute - How to solve this?

I was working on capturing the order of elements contained in tag. Here is all the code:
League.java:
#Root
#Convert(value = LeagueConverter.class)
public class League
{
#Attribute
private String name;
#Element(name="headlines", required = false)
private Headlines headlines;
#Element(name="scores", required = false)
private Scores scores;
#Element(name="standings", required = false)
private Standing standings;
#Element(name="statistics", required = false)
private LeagueStatistics statistics;
public List<String> order = new ArrayList<String>();
// get methods for all variables
}
LeagueConverter.java:
public class LeagueConverter implements Converter<League>
{
#Override
public League read(InputNode node) throws Exception
{
League league = new League();
InputNode next = node.getNext();
while( next != null )
{
String tag = next.getName();
if(tag.equalsIgnoreCase("headlines"))
{
league.order.add("headlines");
}
else if(tag.equalsIgnoreCase("scores"))
{
league.order.add("scores");
}
else if(tag.equalsIgnoreCase("statistics"))
{
league.order.add("statistics");
}
else if(tag.equalsIgnoreCase("standings"))
{
league.order.add("standings");
}
next = node.getNext();
}
return league;
}
#Override
public void write(OutputNode arg0, League arg1) throws Exception
{
throw new UnsupportedOperationException("Not supported yet.");
}
}
Exampe of XML:
<android>
<leagues>
<league name ="A">
<Headlines></Headlines>
<Scores></Scores>
...
</league>
<league name ="B">...</league>
</leagues>
</android>
How I'm calling it and expecting it to behave: (Snippet)
Android android = null;
Serializer serial = new Persister(new AnnotationStrategy());
android = serial.read(Android.class, source);
Log.i("Number of leagues found ",tsnAndroid.getLeagueCount() + ""); // prints fine
League nhl = tsnAndroid.getLeagues().get(0); // works fine
// DOES NOT WORK throws NullPointerEx
League nhl2 = tsnAndroid.getLeagueByName("A");
// DOES NOT WORK throws NullPointerEx
for(String s : nhl.getOrder())
{
Log.i("ORDER>>>>>", s);
}
The problem:
android.getLeagueByName() (Works with #Attribute name) suddenly stops working when I have the converter set, so its like the following from League.java, never gets set.
#Attribute
private String name; // not being set
However, when I comment out the converter declaration in League.java - Every league has an attribute called name and android.getLeagueByName() starts working fine...
Does #Convert for League somehow interfere with #Attribute in League?
Even though this question is outrageously old (as is the SimpleXML library), I will give my two cents.
#Convert annotation works only with #Element, but it does not have any effect on #Attribute. I'm not sure if that's a bug or a feature, but there is another way of handling custom serialized objects - called Transform with Matcher, and it works both with Attributes and with Elements. Instead of using the Converters, you define a Transform class that handles serialization and deserialization:
import java.util.UUID;
import org.simpleframework.xml.transform.Transform;
public class UUIDTransform implements Transform<UUID> {
#Override
public UUID read(String value) throws Exception {
return value != null ? UUID.fromString(value) : null;
}
#Override
public String write(UUID value) throws Exception {
return value != null ? value.toString() : null;
}
}
As you can see, it is more straight-forward than implementing the Convert interface!
Create a similar class for all your objects that require custom de/serialization.
Now instantiate a RegistryMatcher object and register there your custom classes with their corresponding Transform classes. This is a thread-safe object that internally uses a cache, so it might be a good idea to keep it as a singleton.
private static final RegistryMatcher REGISTRY_MATCHER = new RegistryMatcher();
static {
try {
REGISTRY_MATCHER.bind(UUID.class, UUIDTransform.class);
// register all your Transform classes here...
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Finally, you can create a Persister class each time before a conversion and pass it the AnnotationStrategy together with your RegistryMatcher instance. In this factory method below, we will also use an indenting formatter:
private static Persister createPersister(int indent) {
return new Persister(new AnnotationStrategy(), REGISTRY_MATCHER, new Format(indent));
}
Now you can make your serialization/deserialization methods:
public static String objectToXml(Object object, int indent) throws MyObjectConversionException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
Persister p = createPersister(indent);
try {
p.write(object, out, "UTF-8");
return out.toString("UTF-8");
} catch (Exception e) {
throw new MyObjectConversionException("Cannot serialize object " + object + " to XML: " + e.getMessage(), e);
}
}
public static <T> T xmlToObject(String xml, final Class<T> clazz) throws MyObjectConversionException {
Persister p = createPersister(0);
try {
return (T) p.read(clazz, xml);
} catch (Exception e) {
throw new MyObjectConversionException(
"Cannot deserialize XML to object of type " + clazz + ": " + e.getMessage(), e);
}
}
The only issue with this approach is when you want to have different formatting for the same object - e.g. once you want the java.util.Date to have just the date component, while later on you also want to have the time component. Then just extend the Date class, calling it DateWithTime, and make a different Transform for it.
#ElementListUnion will capture the order of elements
The #Convert annotation works only on #Element fields. I am struggling against converting #Attribute fields too but with no success for now...

Intent to get the UID of application uninstalled

i've a receiver that is fired on any application uninstall. I want to get the UID of the application . Currently i got the package name that was uninstalled but when i'm trying to get the UID, its returning null. Currently, i'm getting the UID of any package from following code.
public String getID(String pckg_name) {
ApplicationInfo ai = null;
String id = "";
try {
ai = pm.getApplicationInfo(pckg_name, 0);
id = "" + ai.uid;
} catch (final NameNotFoundException e) {
id = "";
}
return id;
}
You can't get the UID after the package has been uninstalled because it is no longer there. The broadcast Intent is sent after the package has been removed. However...
...from the documentation:
The broadcast Intent that is broadcast when the application is removed (uninstalled) contains an extra EXTRA_UID containing the integer uid previously assigned to the package.

Pass an Exception as a Parcel

I am trying to pass an exception to an activity meant to dump the relevant information to the screen.
Currently I pass it through a bundle:
try {
this.listPackageActivities();
} catch (Exception e) {
Intent intent = new Intent().setClass(this, ExceptionActivity.class).putExtra("Exception", e);
startActivity(intent);
}
But when it gets there:
if (!(this.bundle.getParcelable("Exception") != null))
throw new IndexOutOfBoundsException("Index \"Exception\" does not exist in the parcel." + "/n"
+ "Keys: " + this.bundle.keySet().toString());
This sweet exception is thrown but when I look at the keySet and the bundle details it tells me that there is one parcelable object with a key named "Exception".
I understand that this has something to do with types but I do not understand what I am doing wrong. I just want to dump information about an exception, any exception to the screen. Is there a way to do that without having to condense all the information into a string every time?
I stumbled on this question when I was searching for a method to pass exceptions from a service to an activity. However, I found a better method, you can use the putSerializable() method of the Bundle class.
To add:
Throwable exception = new RuntimeException("Exception");
Bundle extras = new Bundle();
extras.putSerializable("exception", (Serializable) exception);
Intent intent = new Intent();
intent.putExtras(extras);
To retrieve:
Bundle extras = intent.getExtras();
Throwable exception = (Throwable) extras.getSerializable("exception");
String message = exception.getMessage();
The class Exception doesn't implement the Parcelable interface. Unless android is breaking some fundamental Java constructs of which I'm unaware, this means you can't put an Exception as a Parcel into a Bundle.
If you want to "pass" the execption to a new Activity, just bundle up the aspects of it that you're going to need in your new Activity. For example, let's say you just want to pass along the exception message and the stacktrace. You'd so something like this:
Intent intent = new Intent().setClass(this,ExceptionActivity.class)
intent.putExtra("exception message", e.getMessage());
intent.putExtra("exception stacktrace", getStackTraceArray(e));
startActivity(intent);
where getStackTraceArray looks like this:
private static String[] getStackTraceArray(Exception e){
StackTraceElement[] stackTraceElements = e.getStackTrace();
String[] stackTracelines = new String[stackTraceElements.length];
int i =0;
for(StackTraceElement se : stackTraceElements){
stackTraceLines[i++] = se.toString();
}
return stackTraceLines;
}

Categories

Resources