I'm using Parse with my Android app, and my code looks like this:
public class SignupActivity extends Activity {
//Collect signup data
User.signupInBackground(data);
}
public class User{
//User methods, constructors, etc
public static void signup(data){
ParseUser pUser = new ParseUser();
//Build data into pUser
pUser.signUpInBackground(new SignUpCallback() {
public void done(ParseException e){
if (e!=null){
Log.v("Signup",e.toString());
}
}
});
So the question is, how do I notify my activity when the signUpInBackground process is complete? I can't have SignupActivity implement SignUpCallback because it's an abstract class and I have to extend Activity.
Ultimately, what I'm trying to do is display a dialog box or waiting animation, and then get rid of it when the background thread is done. Alternatively, the background thread could launch an activity. The problem with this is that the User class and the anonymous inner class don't have their own Context, so they can't start activities.
I'm fairly new at this, so thanks for your help.
Several approaches might work given your current code structure.
Create a Handler in SignupActivity and pass that to the User so it has a way of interacting with the activity.
Make SignUpCallback an interface instead of an abstract class.
Create an instance of a concreate subclass of SignUpCallback in your SignupActivity class; it will have access to the methods of SignupActivity.
I'm assuming that signUpInBackground is executing on a worker thread and that the callback is invoked from that thread. If that's correct, then in all cases you will need to interact with SignupActivity through a Handler. Thus, I'd suggest method #1 unless the other approaches allow for cleaner code.
Related
I'm using an ASyncTask in my app to get some data (a short URL) via a REST API from a web service (Bitly).
When the ASyncTask completes I want to pass the result back to my MainActivity.
Getting the data back to the MainActivity is acheievd by using the onPostExecute method of the AsyncTask.
I've read and read and read about how to do this and there seem to be two general approaches.
Originally I was using a 'WeakReference' approach whereby at the start of the AsyncTask class you create a weak reference to your MainActivity as follows:
private class getShortURL extends AsyncTask<String, Void, String> {
private WeakReference<MainActivity> mainActivityWeakReference;
myASyncTask(MainActivity activity) {
mainActivityWeakReference = new WeakReference<>(activity);
}
{etc etc}
With this approach your AsyncTask class sits outside of your MainActivity class and so a lot of things need to be referenced via the weak reference.
This worked fine (except I suspected - possibly incorrectly - that this weak reference may have been the cause of occassional NPEs), but I then found another way of doing things.
This second approach involved moving the ASyncTask class inside of the MainActivity class.
This way I was able to access everything that was accessible in the MainActivity class directly, inlcuding UI elements and methods defined in the MainActivity. It also means that I can access resources such as strings etc and can generate toasts to advise the user what is happening.
In this case the whole of the WeakReference code above can be removed and the AsyncTask class can be made private.
I am also then able to do things like this directly in onPostExecute or to keep this in a method within the MainActivity that I can call directly from onPostExecute:
shorten_progress_bar.setIndeterminate(false);
shorten_progress_bar.setVisibility(View.INVISIBLE);
if (!shortURL.equals("")) {
// Set the link URL to the new short URL
short_link_url.setText(shortURL);
} else {
CommonFuncs.showMessage(getApplicationContext(), getString(R.string.unable_to_shorten_link));
short_link_url.setHint(R.string.unable_to_shorten_link);
}
(note that CommonFuncs.showMessage() is my own wrapper around the toast function to make it easier to call).
BUT, Android Studio then gives a warning that "the AsyncTask class should be static or leaks might occur".
If I make the method static I then get a warning that the method from the MainActivity that I want to call from onPostExecute cannot be called as it is non-static.
If I make that method from MainActivity a static method, then it cannot access string resources and any other methods that are non static - and down the rabbit hole I go!
The same is true, as you would expect, if I just move the code from the method in the MainActivity into the onPostExecute method.
So...
Is having an AsyncTask as a non-static method really a bad thing? (My
app seems to work fine with this warning in AS, but I obviously don't
want to be creating a memory leak in my app.
Is the WeakReference appraoch actually a more correct and safer approach?
If I use the WeakReference approach, how can I create things like toasts which need to be run on the UI thread and access string
resources etc from the MainActivity?
I read somewhere about creating an interface but got a bit lost and couldn't find that again. Also would this not have the same kind of reliance on the MainActivity that a WeakReference does and is that a bad thing?
I'm really looking for best practice guidance on how to get some data back to the MainActivity and the UI thread from an AsyncTask that is safe and doesn't risk memory leaks.
Is having an AsyncTask as a non-static method really a bad thing? (My app seems to work fine with this warning in AS, but I obviously don't want to be creating a memory leak in my app.
Yes, your Views and your Context will leak.
Enough rotations and your app will crash.
Is the WeakReference approach actually a more correct and safer approach?
It's lipstick on a dead pig, WeakReference in this scenario is more-so a hack than a solution, definitely not the correct solution.
What you're looking for is a form of event bus from something that outlives the Activity.
You can use either retained fragments* or Android Architecture Component ViewModel for that.
And you'll probably need to introduce Observer pattern (but not necessarily LiveData).
If I use the WeakReference approach, how can I create things like toasts which need to be run on the UI thread and access string resources etc from the MainActivity?
Don't run that sort of thing in doInBackground().
I'm really looking for best practice guidance on how to get some data back to the MainActivity and the UI thread from an AsyncTask that is safe and doesn't risk memory leaks.
The simplest way to do that would be to use this library (or write something that does the same thing yourself, up to you), put the EventEmitter into a ViewModel, then subscribe/unsubscribe to this EventEmitter inside your Activity.
public class MyViewModel: ViewModel() {
private final EventEmitter<String> testFullUrlReachableEmitter = new EventEmitter<>();
public final EventSource<String> getTestFullUrlReachable() {
return testFullUrlReachableEmitter;
}
public void checkReachable() {
new testFullURLreachable().execute()
}
private class testFullURLreachable extends AsyncTask<Void, Void, String> {
...
#Override
public void onPostExecute(String result) {
testFullUrlReachableEmitter.emit(result);
}
}
}
And in your Activity/Fragment
private MyViewModel viewModel;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = ViewModelProviders.of(this).get(MyViewModel.class);
// ...
}
private EventSource.NotificationToken subscription;
#Override
protected void onStart() {
super.onStart();
subscription = viewModel.getTestFullUrlReachable().startListening((result) -> {
// do `onPostExecute` things here
});
}
#Override
protected void onStop() {
super.onStop();
if(subscription != null) {
subscription.stopListening();
subscription = null;
}
}
I have a class that handles Bluetooth communication and this class needs to forward data that it received from Bluetooth to my activity class. I used to pass this data via Broadcasts but now I am trying to pass the data via an interface. The problem I am having is that upon receiving the data in my activity, I can`t make changes in the UI and I am getting an error that says "Only the original thread that created a view hierarchy can touch its views."
//My Activity class implements an interface and receiveDataFromBluetoothConnection is a method in that interface
//My bluetooth service sends data to my activity via this method.
public void receiveDataFromBluetoothConnection(String data) {
processIncomingBtMessage(data);
}
private void processIncomingBtMessage(String incomingMessage) {
//...
//...
//...
if (message == BtMessageIn.BT_MESSAGE_IN_SYSTEM_OFF) {
Log.d(TAG, "remoteControlBubblePillar: bReceiver: Setting Button to On");
arduinoPowerStatus = false;
LightsButtonsBackgroundUnpressed();
btnOnOff.setBackgroundResource(R.drawable.button_shape_round_corners_gradient_green);
btnOnOff.setText(R.string.On);
}
//...
//...
//...
}
Here is the full error message that I get
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7913)
at android.view.ViewRootImpl.invalidateChildInParent(ViewRootImpl.java:1373)
at android.view.ViewGroup.invalidateChild(ViewGroup.java:5449)
at android.view.View.invalidateInternal(View.java:14825)
at android.view.View.invalidate(View.java:14761)
at android.view.View.invalidateDrawable(View.java:19051)
at android.widget.TextView.invalidateDrawable(TextView.java:6353)
at android.graphics.drawable.Drawable.invalidateSelf(Drawable.java:436)
at android.graphics.drawable.Drawable.setVisible(Drawable.java:820)
at android.view.View.setBackgroundDrawable(View.java:19522)
at android.support.v7.widget.AppCompatButton.setBackgroundDrawable(AppCompatButton.java:86)
at android.view.View.setBackground(View.java:19498)
at android.view.View.setBackgroundResource(View.java:19481)
at android.support.v7.widget.AppCompatButton.setBackgroundResource(AppCompatButton.java:78)
at com.bubblewall.saik.bubblewall.remoteControlBubblePillar.processIncomingBtMessage(remoteControlBubblePillar.java:354)
at com.bubblewall.saik.bubblewall.remoteControlBubblePillar.receiveDataFromBluetoothConnection(remoteControlBubblePillar.java:275)
at com.bubblewall.saik.bubblewall.BluetoothConnection$1.onCharacteristicChanged(BluetoothConnection.java:97)
at android.bluetooth.BluetoothGatt$1.onNotify(BluetoothGatt.java:400)
at android.bluetooth.IBluetoothGattCallback$Stub.onTransact(IBluetoothGattCallback.java:177)
at android.os.Binder.execTransact(Binder.java:573)
Any ideas how can I fix this issue? Or should I just switch back to Broadcasts?
The problem is that your Bluetooth class doesn't run on the main/UI thread, as the activity does. In order to avoid race conditions, Android crashes the app to let you know that you're doing something wrong.
Fortunately, there is a simple way to fix this by using runOnUiThread(Runnable):
public void receiveDataFromBluetoothConnection(String data) {
runOnUiThread(() -> processIncomingBtMessage(data));
}
But I have a better suggestion. Instead of letting the activity handle this, it would be better to design your other class such that it makes sure that when it notifies your activity it does so on the proper thread.
new Handler(Looper.getMainLooper()).post(() -> listener.receiveDataFromBluetoothConnection(data))
It's better to do it this way because in case other activities use the Bluetooth class they don't have to worry about making sure they execute code on the proper thread, the Bluetooth class already handles that.
For a more in-depth explanation on exactly what's happening and what the proper way to handle it is you can read Communicating with the UI Thread, on developer.android.com.
This is likely an issue with me not properly understanding threading, however I often try and group code into a seperate class and become hindered by some it not being on the UI thread. I can't find anything that properly explains this however. As an example:
public class MyActivity extends Activity {
#Override
public void onCreate (Bundle bundle) {
HelperClass = new HelperClass(context);
}
}
public class HelperClass extends ContextWrapper {
someMethod () {
// Do stuff
}
}
When someMethod is called, if it's trying to do something thread dependant, I will get an error. I've had this with trying to keep some WebView logic in a seperate class as well as trying to access a realm database.
Using runnables seems very messy and I clearly don't understand exactly what's happening to beable to properly structure my code.
Can anyone explain why? and what the best soloution is?
I have a fragment class:
public class UploadFragmentOne extends Fragment {}
I have subclassed:
public interface Communicator {
void communicate(int position);
}
In the onCreateView:
((Communicator) getActivity()).communicate(1);
The hosting activity signature:
public class DetailsPager extends FragmentActivity implements UploadFragmentOne.Communicator {}
Member function in the above activity:
public void communicate(int position) {
Toast.makeText(DetailsPager.this, "Clicked " + position, Toast.LENGTH_LONG).show();
}
This works like a charm, but I dont understand HOW? Sorry this might be too dumb, but I want to know how the control flows in this?
You should have a look on the following link once.-
Now come to your query
(MyActivity)activity.some_method()
Now suppose you are going to attach the same fragment for another activity say MainActivity then you need to do like -
(MainActivity)activity.some_method()
Now suppose some other activity are implementing the same fragment then each time you need to use the instanceOf check and then call the method and also add it.
Again suppose a trivial case when you are going the create a library and you are going to offer some result delivery for the particular event then?
How will you get the instance type ?
interface as the name suggests offer a way to be communicated without the actual class instance. They only need to be implemented. You can have a look on the OnClickListener in the View class of android API sources.
I’m trying to call a method in my main Activity from another file, but it crashes. I can easily do this the other way. Can you not invoke methods of the main Activity from another Activity?
public class MainActivity extends Activity {
….
……..
……
public void my_function(String a){
//do some stuff
}
}
package main;
public class stuff extends Activity {
….
….
…
MainActivity run = new MainActivity();
run.my_function(String a);
}
}
If you want a method to get called from different classes, create a seperate class
and encapsulate the logic. See an activity more as a view controller, the logic
should be somewhere else.
Sorry for my english, hope this helps.
Steve
The problem is this line
MainActivity run = new MainActivity();
you never instantiate Activities directly, you need to instantiate them through Intent objects.
Intent intent = new Intent(this, MainActivity.class);
If you want to instantiate a new activity use an Intent. If you want to call my_function in an activity that is already instantiated, you need first to get a pointer to that Activity, which in Android is a little bit tricky.
Additionally, the fact that you want to call a method from another activity is a sign of bad design, or that you don't fully understand the Android way of doing things.
Think if there's any other approach you can use for your purpose.