I have an activity that contains fragments that are added at runtime. Also activity is connected to the tcp server. When I swipe the fragment server sends a message in a different thread. Then I want to change the button colors inside the fragment in this thread. I have written the code in fragment below:
public class changeBtn implements Runnable
{
#Override
public void run() {
button = (Button) fragmentView.findViewWithTag("Button1");
}
}
public void startProg(String[] array)
{
new Thread(new changeBtn()).start();
}
This is the class that works async in activity for tcp
public class connectTask extends AsyncTask<String,String,TCPClient> {
#Override
protected TCPClient doInBackground(String... message) {
//we create a TCPClient object and
mTcpClient = new TCPClient(new TCPClient.OnMessageReceived() {
#Override
//here the messageReceived method is implemented
public void messageReceived(String message) {
//this method calls the onProgressUpdate
srvrMessage = message;
if(srvrMessage!=null&&message!=null) {
acikMasalar = srvrMessage.split("\\*");
mesajGeldi = true;
FragmentMasaDesing fragment=null;
if(mesajGeldi) {
int a =mViewPager.getCurrentItem();
fragment = (FragmentMasaDesing) getSupportFragmentManager()
.getFragments().get(mViewPager.getCurrentItem());
}
if(fragment!=null) {
fragment.startProg();
}
}
}
});
mTcpClient.run();
return null;
}
}
I am running this code inside the thread that works in activity. It throws me this error:"Only the original thread that created a view hierarchy can touch its views."
How can I change the button color from another thread in activity?
Related
MyThread class is used to change the value of the myValue attribute in ActivityTwo Class.
public class MyThread implements Runnable {
private ActivityTwo activity;
public MyThread(ActivityTwo activity) {
this.activity = activity;
}
#Override
public void run() {
while(true){
activity.setValue(2);
}
}
}
ActivityTwo is an AppCompatActivity activity which runs as the main Thread.
public class ActivityTwo extends AppCompatActivity {
private MyThread myThread;
private Button startLogBtn;
private int myValue;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_two);
startLogBtn = findViewById(R.id.startLogBtn);
myThread = new MyThread(this);
startLogBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
new Thread(myThread).start();
}
});
}
public void setValue(int value){
this.myValue= value;
}
}
When I Click the startLogButton it goes to a white screen and then restart the app. What should I do? I have no idea what has gone wrong.
Thanks in advance.
The issue was I have violated the rule "Do not access the Android UI toolkit from outside the UI thread".
ref : https://developer.android.com/guide/components/processes-and-threads
As a solution I found that I can use thread communication using message passing.
ref : Communication between UI thread and other threads using handler
I start a Thread in a Fragment and use an Interface call, to #Override a method in the Fragment starting the thread. This #Override stops a ProgressDialog and changes the Text of a TextView in the Fragment.
When I do the same in an Activity, there is no Problem but now when using a Fragment I got the "only Thread that creates a View can touch it's views" - Error. So I used getActivity().runOnUiThread(runnable) and posted the code to the MainThread, but why do I need to do this, since it works in a Activity without this? Did I made a mistake?
The Thread
//interface
private ConnectToDevice connectToDevice;
//C-Tor
public Thread_ConnectToDevice(BluetoothDevice device, ConnectToDevice connectToDevice ) {
this.mBluetoothDevice = device;
this.connectToDevice = connectToDevice;
}
//call
connectToDevice.connectionSuccess(false, null);
Fragment
//make Thread
thread_connectToDevice = new Thread_ConnectToDevice(mBluetoothDevice, Fragment_RoutineStartConnection_setPassword.this);
thread_connectToDevice.start();
//CallBack
//Thread Connect Success
#Override
public void connectionSuccess(final Boolean bSuccess,final BluetoothSocket mSocket) {
getActivity().runOnUiThread(new Runnable() {
#Override
public void run() {
if(bSuccess){
mProgessDialog.setTitle(R.string.tv_Fragmentsetpassword_Connected_CheckPW);
if(thread_connectedToDevice != null){
if(thread_connectedToDevice.isAlive()){
thread_connectedToDevice.interrupt();
}
}
thread_connectedToDevice = new Thread_ConnectedToDevice(mSocket, sTryingDonglePassword);
thread_connectedToDevice.start();
}else{
mProgessDialog.dismiss();
tv_Fragmentsetpassword_userhint
.setTextColor(getResources().getColor(R.color.Mercedes_RED, null));
tv_Fragmentsetpassword_userhint.setText(R.string.tv_Fragmentsetpassword_ConnectionFailed);
}
}
});
}
I have the feeling that I passed the wrong listener Instance to the Thread.
As asked this is the callback realized the same way but in a Activity:
Thread
//listener
private Finished_AskingForInformation listener;
//C-Tor
public Td_AskForInformation(
Finished_AskingForInformation listener) {
this.listener = listener;
}
//call
listener.AskingFinished();
Activity
//Create and start thread
td_askForInformation = new Td_AskForInformation(this);
td_askForInformation.start();
//CallBack
#Override
public void AskingFinished() {
mProgressDialog.dismiss();
}
I've created an interface which holds a reference to an interfaces instantiated from an activity.
This is the interface:
public interface Calback {
void fun();
}
This is the activity which instantiates the calback and binds it to asincktask.
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView txt = findViewById(R.id.helloTxtv);
txt.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Calback call = new Calback() {
#Override
public void fun() {
Log.d("tag","text of destroyed activity: "+((TextView)findViewById(R.id.helloTxtv)).getText());
}
};
Worker worker = new Worker(call);
worker.execute();
}
});
}
}
What's strange is that using that calback I can access textview even if the activity was destroyed.
This is the code from asyncktask:
public class Worker extends AsyncTask<Void, Void, Void> {
private final Calback call;
public Worker(Calback call) {
this.call = call;
}
#Override
protected Void doInBackground(Void... voids) {
try {
sleep(5000);
Log.d("tag","done");
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
call.fun();
}
}
To ensure that the activity it's destroyed I've just rotated the screen.(But I've got the same result after starting another activity and finish the current one)
And here is the log result.
PS: I've used Android Studio 3.0
If you are able to access the text of the TextView after the parent Activity has been destroyed, then you have a memory leak.
However, I'm not convinced that is what is going on here. I think it is more likely that either the activity has not been destroyed, or the activity's state was persistent and you are now looking at the state in the new (reincarnated) activity.
Why? Because, it seems that the callback is being called via an onClick listener for the text view. And that can only occur if the specific text view is still visible. It can't be visible if it is a component of a destroyed activity.
I have an activity with multiple AsyncTask's, but when i press back button, the Activity is reloaded and the AsyncTask's are executed again. what should i do to Back to the previous activity and not reload the activity and asynctask ? please help.
public class LugarActivity extends SherlockActivity {
CargarDatos cargarDatos;
CargarComentarios cargarComentarios;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lugar);
cargarDatos = new CargarDatos();
cargarCometarios = new CargarComentarios();
loadData();
}
public void loadData(){
cargarDatos.execute();
}
public void loadOtherData(){
cargarComentarios.execute();
}
public class CargarDatos extends AsyncTask<Integer, Integer, String>{
#Override
protected String doInBackground(Integer... params) {
// here download data
}
#Override
protected void onPostExecute(String html) {
loadOtherData();
}
}
public class CargarComentarios extends AsyncTask<Integer, Integer, String>{
#Override
protected String doInBackground(Integer... params) {
// here download data
}
}
}
FIXED!
i fixed the problem with Singleton class:
public class DataManager {
private static DataManager instance = null;
protected static boolean isShowingTheView = false;
protected DataManager() { }
public static synchronized DataManager getInstance() {
if (instance == null) {
instance = new DataManager();
}
return instance;
}
}
in the activity i add this code:
DataManager dataManager = new DataManager();
if(!dataManager.isShowingTheView){
loadData();
dataManager.isShowingTheView = true;
}else{
finish();
}
and finally i override the onDestroy() method
#Override
public void onDestroy(){
dataManager.isShowingTheView = false;
super.onDestroy();
}
Remove loadData() from onCreate and call somewhere else.
Use Fragments
http://www.androiddesignpatterns.com/2013/04/retaining-objects-across-config-changes.html
A fragment can stay in memory during a configuration change and therefore you can run your asynctask inside itself. You can then query the fragment for any state information you require from your tasks and update your Activity accordingly.
If your Activity is destroyed before the other activity starts, using the back button will call onCreate again, instead of onRestart or onResume.
See here for details.
As Kuffs already mentions, using Fragments is the way to go.
Uglier solution, you could also set a shared preference holding a boolean once your AsyncTask is launched (or on its onPostExecute) so that it won't launch again after checking for that preference on your Activity's onCreate.
I would like to enable a few buttons from my main activity once the stuffs from doInBackground() is finished! Can someone please let me know how to do that?
I can't use findViewByID() for making he button visible from the AsyncTask class as it's not an activity class! :/
Do Like this...
Define a method which enables the Buttons.
Then on PostExecute() on AsyncTask, call that method
there is one callback onPostExecution(...) { } of AsynTask class use this method to UI stuff,for enable,disable button just write this way in onPostExcustion(...)
runOnUiThread(new Runnable() {
#Override
public void run() {
// TODO Auto-generated method stub
}
});
also make sure this method only available in activity class
thank you
Follow this way:
[1] Create your AsyncTask :
public class performBackgroundTask extends AsyncTask<Void, Void, Void> {
ProgressDialog Dialog = new ProgressDialog(HotUsers.this);
protected void onPreExecute() {
Dialog.setMessage("Loading Hot Users...");
Dialog.show();
}
protected void onPostExecute(Void unused) {
if(Dialog.isShowing())
Dialog.dismiss();
set_details_on_screen();
}
#Override
protected Void doInBackground(Void... params) {
get_details_from_server(); // get data like userid,username,userdesc etc...
return null;
}
}
[2] That will call function to proceed for UI changes.
public void set_details_on_screen()
{
if(userid > 0 )
handler_default.sendEmptyMessage(0);
else
handler_default.sendEmptyMessage(1);
}
[3] At last your UI changes will be reflected on screen with this Handler.
private Handler handler_default = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case 0: {
textuserid = (TextView) findViewById(R.id.userid);
textusername = (TextView) findViewById(R.id.username);
textuserdesc = (TextView) findViewById(R.id.userdesc);
textuserid.setText(userid);
textusername.setText(username);
textuserdesc.setText(userdesc);
break;
}
case 1: {
Toast.makeText(getApplicationContext(),"Error",Toast.LENGTH_LONG).show();
break;
}
}
}
};
Thanks.
your class which extends AsyncTask you can push your context into it, when calling the execute().
private class RegisterUser extends AsyncTask<String,String,String> {
private ListActivity activity;
public RegisterUser(ListActivity activity) {
this.activity = activity;
}
protected void onPostExecute(JSONObject json) {
activity.editText = (EditText)activity.findViewById(R.id.editText1);
//or
activity.enableButton();
}
}
and call the execute from the Activity like this:
new RegisterUser(this).execute(new String[] {"param"});
or you can define the AsyncTask class inside your Activity class - where you can reach everything.
more info Lars Vogel - Android Threads, Handlers and AsyncTask