37

I am new to Android/Java programming. I have two classes, one is an Activity and the other is a normal class. My activity class contains a TextView. Can I update the TextView of the activity class from a normal class? I tried with random code, but it fails.

// activity class
public class MainMenu extends Activity {
    public TextView txtView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TextView txtView = (TextView)findViewById(R.id.text);   
    }
}

// Other class
public class ClassB {
    public ClassB() {
        public void Update() {
            TextView txtView = (TextView)findViewById(R.id.text);
            txtView.setText("Hello");
        }
    }
}
0xCursor
  • 2,242
  • 4
  • 15
  • 33
Riskhan
  • 4,434
  • 12
  • 50
  • 76

9 Answers9

64

You have to pass the Context reference via constructor.

public class ClassB {
   Context context;
   public ClassB(Context context){
     this.context=context;
   }

   public void Update(){
        TextView txtView = (TextView) ((Activity)context).findViewById(R.id.text);
        txtView.setText("Hello");
   }
KV Prajapati
  • 93,659
  • 19
  • 148
  • 186
34

The preceding two examples require TextView to be used directly within the other class. However, there are cases where TextView shouldn't be present in the other class, e.g., your ClassB is used to update various Activities, where some activities update TextViews, and others might update EditTexts.

Hence, the below solution can guide you on how you could decouple your TextView from other classes, yet, you could still achieve what you want. It's using the interface approach.

Firstly, declare an interface where you could have ClassB communicate to the Activity, and call it MyCallback:

public interface MyCallback {
    // Declaration of the template function for the interface
    public void updateMyText(String myString);
}

Next in your Activity, implement MyCallback, and hence its function definition. In this function, you will receive the String from ClassB that you could do whatever you like, e.g., update the TextView (or EditText, etc.):

public class MyActivity extends AppCompatActivity implements MyCallback {
    // ... whatever code of your activity

    @Override
    public void updateMyText(String myString) {
        ((TextView)findViewById(R.id.text)).setText(myString);
    }
}

Lastly, you could declare ClassB that takes in MyCallback (i.e., your Activity class object that is also a MyCallback). From there you could use ClassB to communicate back to Activity and get it to update its TextView through the updateMyText function:

public class ClassB {
    MyCallback myCallback = null;

    public ClassB(MyCallback callback) {
        this.myCallback = callback;
    }

    public void doSomething() {
        // Do something to get String
        String myString = str;

        if (myCallback != null) {
            myCallback.updateMyText(myString);
        }
    }
}

Hope this helps better show the architected structure of decoupling the Activity properly from ClassB.

0xCursor
  • 2,242
  • 4
  • 15
  • 33
Elye
  • 53,639
  • 54
  • 212
  • 474
  • In which class did you place the interface? Thanks – pewpew Feb 17 '18 at 18:35
  • 3
    You could place it as a top level interface or it could live within ClassB – Elye Feb 18 '18 at 02:17
  • This solution works perfectly on Emulator, but it does not update on my physical device as soon as `myString` is fired into interface function. – Aliton Oliveira Mar 06 '19 at 00:23
  • from which class are you calling this? `public ClassB(MyCallback callback) { this.myCallback = callback;` }' – RobinHood Jan 07 '20 at 06:55
  • @RobinHood, You can instantiate `ClassB` from `MyActivity` – Elye Jan 09 '20 at 12:01
  • Instead of implement MyCallback in the activity, i created an instance of the interface MyCallback and implement/override the methods so that i could pass the object when i instantiate ClassB from MyActivity. MyCallback mc = new MyCallback() {override the methods......} then ClassB cb = new ClassB(mc); – bohbian Mar 09 '20 at 10:29
21

This is actually a deceptively "simple" question, but in reality a complicated problem in the context of Android development.

Activities are the "process entry point", meaning that any Activity you see can act as the "first point of entry to your application on start-up". People think that only the Activity that has the MAIN/LAUNCHER intent filter can be launched at start-up, but this is false.

Any Activity can act as the "first Activity", because Android can restart it from any point with the current active navigation stack.

Anyways, with that in mind, an Activity can show a View, and people often use the Activity to hold each screen of their app (instead of using it as an entry point, and swapping out view controllers in it ~ fragments).

So if you have multiple Activities, then you need to share data between them in such a way, that you take it into consideration that both activities can be started up at any time as the first Activity of the app.


For this, what you need to do is not "set the text view's text directly from another class", but you need to modify observable shared data.

The newly released official Android Architecture Components provide the LiveData<T> class, which has a subclass called MutableLiveData<T>.

To update the data from one class to another Activity, what you must do is have a global data exposed as a LiveData

public class MyApplication extends Application {
    private static MyApplication INSTANCE;

    DataRepository dataRepository; // this is YOUR class

    @Override
    public void onCreate() {
        super.onCreate();
        INSTANCE = this;
        dataRepository = new DataRepository();
    }

    public static MyApplication get() {
        return INSTANCE;
    }
}

The DataRepository should expose LiveData:

public class DataRepository {
    private final MutableLiveData<MyData> data = new MutableLiveData<>();

    public LiveData<MyData> getMyData() {
        return data;
    }

    public void updateText(String text) {
        MyData newData = data.getValue()
                             .toBuilder() // immutable new copy
                             .setText(text)
                             .build();
        data.setValue(newData);
    }
}

Where the Activity subscribes to this:

public class MyActivity extends BaseActivity {
    DataRepository dataRepository;

    TextView textView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        MyApplication app = (MyApplication)getApplicationContext();
        dataRepository = app.getDataRepository();

        setContentView(R.layout.main_activity);
        textView = findViewById(R.id.textview);

        dataRepository.getMyData().observe(this, new Observer() {
            @Override
            public void onChange(MyObject myObject) {
                textView.setText(myObject.getText());
            }
        }
    }

So to update this text, you need to get the DataRepository class, and call updateText on it:

DataRepository dataRepository = MyApplication.get().dataRepository();
dataRepository.updateText("my new text");

And this will properly update your Activity text view.

Beware that you should also persist the data to onSaveInstanceState(Bundle so that it is not lost (assuming the data is not from disk).

To do that, you need to do the following:

public class BaseActivity extends AppCompatActivity {
    DataRepository dataRepository;

    private static boolean didRestoreGlobals = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        MyApplication app = (MyApplication)getApplicationContext();
        dataRepository = app.getDataRepository();

        super.onCreate(savedInstanceState);
        if(!didRestoreGlobals) {
            didRestoreGlobals = true;
            if(savedInstanceState != null) {
                dataRepository.restoreState(savedInstanceState.getBundle("dataRepository"));
            }
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle bundle) {
        super.onSaveInstanceState(bundle);
        bundle.putBundle("dataRepository", dataRepository.saveState());
    }
}

And then add saveState/restoreState methods to DataRepository accordingly.

EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
  • 1
    Could you give an example of what MyData would look like? – Gideon Sassoon Dec 09 '17 at 22:16
  • 1
    Any Kotlin data class, or autovalue generated value classes. Or just some random POJO with fields that has getters/setters. – EpicPandaForce Dec 09 '17 at 22:19
  • Okay, I think I'd fit into the POJO, but really the value I'm trying to change doesn't have a stored counterpart as it's a calculated value off existing methods from the POJO, I'm running into trouble in which toBuilder can't resolve. – Gideon Sassoon Dec 09 '17 at 22:31
  • Then just add the proper getter method :O – EpicPandaForce Dec 10 '17 at 01:16
  • This is superior to using LocaBroadcastManager? – XMAN Dec 09 '18 at 23:52
  • 2
    @XO. I think LocalBroadcastManager can lose events when no one is subscribed at the time of sending the event, no? LiveData preserves the latest-most one. I haven't used LocalBroadcastManager in ages because you need an Intent for it, while you could just use an EventBus (though I cannot tell you if LBM works across processes or not, event buses definitely don't) – EpicPandaForce Dec 10 '18 at 00:17
  • 1
    @XO. actually LBM was deprecated *because* it seems like it might work across processes but it doesn't. So yes, this is now the preferred alternative compared against LocalBroadcastManager, which *seemed* "nicer" only because you could get the instance of LBM from a Context *anywhere* – EpicPandaForce Jan 02 '19 at 08:34
  • @EpicPandaForce: +1, thanks! Well that sucks. Where's the reference to LBM being deprecated? Could not locate this on the Android Developer site. – XMAN Jan 02 '19 at 12:57
  • 1
    @XO. https://developer.android.com/jetpack/androidx/androidx-rn#2018-dec-17-local-bc-mgr – EpicPandaForce Jan 02 '19 at 13:03
  • Hi @EpicPandaForce well this solutions seems looking good but is it recommended? in my case we are also using two activities, where foreground activity is transparent and background activity has some task which run continuously, so idea is to send data from foreground activity to background activity to handle some controls, – Min2 Mar 22 '21 at 13:02
  • If you are sharing state between multiple Activities, you don't really have any other choice – EpicPandaForce Mar 22 '21 at 13:24
  • yeah thats true, but as I was reading about new Sharedflow and StateFlow, whats your thought on the same, does this SharedFlow (observer) make sense to use for this problem across the activities? – Min2 Mar 23 '21 at 05:10
  • MutableStateFlow, BehaviorRelay and MutableLiveData are conceptually very similar, although StateFlow applies `distinct` on its emissions (while `MutableLiveData` can only emit if at least 1 observer is active) – EpicPandaForce Mar 23 '21 at 08:57
  • @EpicPandaForce what does this means --> app.getDataRepository(), can you please update the code, if any changes – Kumararaja Sep 22 '21 at 11:45
  • it's possibly a singleton if you make one like that, because the only shared scope between 2 activities is application scope / singleton scope – EpicPandaForce Sep 23 '21 at 05:56
3

If you are creating an object of your other class(ClassB)inside activity class, the simplest solution is to pass the TextView through constructor (if you aren't create an object in the activity class this answer will not be helpful). So your example should be like below:

// activity class
public class MainMenu extends Activity {
    public TextView txtView;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        txtView = (TextView)findViewById(R.id.text);
        //instantiating a object of the ClassB and passing tv
        ClassB obj = new ClassB(txtView);
    }    

}

// other class
public class ClassB {
   //declarre tv
   TextView txtView;

   //get the tv as arg
   public ClassB(TextView tv){
        txtView = tv;
   }

   public void Update(){
        txtView.setText("Hello");
   }
}
Blasanka
  • 21,001
  • 12
  • 102
  • 104
2

You can make a getter method in your Activity.

In your Activity class:

public TextView getTextView()
{

TextView txtView = (TextView)findViewById(R.id.text);
return txtView;
}

In your ClassB class:

public void Update()
{
          MainMenu obj = new MainMenu();
          TextView tv = obj.getTextView();
          tv.setText("hello");

}
Kazekage Gaara
  • 14,972
  • 14
  • 61
  • 108
  • `new MainMenu()` where MainMenu is an Activity? This answer couldn't possibly be more incorrect. – EpicPandaForce Jan 02 '19 at 08:40
  • The trick is that if MainActivity is actually an activity, then it should be started with an intent. But if it is just a regular class, then if you edit the post accordingly, I'll also remove the downvote - as it stands, it *might* be promoting very bad practices – EpicPandaForce Jan 03 '19 at 08:53
1

I have a XML page (Bert.XML) with four TextViews with ID's TextView1id, TextView2id, TextView3id and TextView4id

 <GridLayout
    android:id = "@+id/gridLayout"
    android:layout_width = "match_parent"
    android:layout_height = "wrap_content"
    android:paddingTop="10dp">

    <TextView
        android:id = "@+id/TextView1id"
        android:layout_gravity="end"
        android:hint = "@+id/Risico"
        android:textSize="@dimen/edit_size"
        android:layout_height = "wrap_content"
        android:layout_width = "fill_parent"
        android:layout_column = "0"
        android:layout_row = "1"
        android:layout_columnSpan = "3"
        />
    <TextView
        android:id = "@+id/TextView2id"
        android:layout_gravity="end"
        android:hint = "@+id/Risico"
        android:textSize="@dimen/edit_size"
        android:layout_height = "wrap_content"
        android:layout_width = "fill_parent"
        android:layout_column = "0"
        android:layout_row = "2"
        android:layout_columnSpan = "3"
        />
    <TextView
        android:id = "@+id/TextView3id"
        android:layout_gravity="end"
        android:hint = "@+id/Risico"
        android:textSize="@dimen/edit_size"
        android:layout_height = "wrap_content"
        android:layout_width = "fill_parent"
        android:layout_column = "0"
        android:layout_row = "3"
        android:layout_columnSpan = "3"
        />
    <TextView
        android:id = "@+id/TextView4id"
        android:layout_gravity="end"
        android:hint = "@+id/Risico"
        android:textSize="@dimen/edit_size"
        android:layout_height = "wrap_content"
        android:layout_width = "fill_parent"
        android:layout_column = "0"
        android:layout_row = "4"
        android:layout_columnSpan = "3"
        />

</GridLayout>

The code for this view is shown below. In here I change the text of the TextViews through the Mail Class. The Activity has been given as a parameter for the Mail Class

package nl.yentel.finekinney;
import android.app.Activity;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.widget.TextView;

public class Bert extends AppCompatActivity {
private TextView theObject;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_bert);

        //both findViewByID work
    theObject = this.findViewById(R.id.TextView2id);
    theObject = findViewById(R.id.TextView2id);

    Mail theMail=new Mail();
    theMail.activity=this;
    theMail.NameOfObject="TextView2id";
    theMail.KindOfObject="TextView";
    theMail.Mail();

    CalculateFromClass(this);
    Calculate(this);
}
//Calculate(dezeActiviteit);
public void Calculate(Activity dezeActiviteit) {
//here you should include dezeActiviteit which can be called from the Class
    theObject = dezeActiviteit.findViewById(R.id.TextView1id);
    theObject.setText("text from method");
}
public void CalculateFromClass(Activity dezeActiviteit) {
    //here you should include dezeActiviteit which can be called from the Class
    theObject = dezeActiviteit.findViewById(R.id.TextView4id);
    theObject.setText("text from Class");
    }
}

My Mail Class looks like this

package nl.yentel.finekinney;
import android.app.Activity;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;

public class Mail extends AppCompatActivity {
    public String NameOfObject;
    public String KindOfObject;
    public Activity activity;

void Mail() {
    //if the name and kind has been given as an input parameter
    int ressourceId = activity.getResources().getIdentifier(NameOfObject, "id", activity.getPackageName());
    if (KindOfObject.equals("TextView")) {
        TextView TextViewObject = activity.findViewById(ressourceId); //VISArB 14
        TextViewObject.setText("this is a TextView");
    }
    if (KindOfObject.equals("EditText")) {
        EditText EditTextObject = activity.findViewById(ressourceId); //VISArB 14
        EditTextObject.setText("this is an EditText");
    }
    //if the name is hard coded
    TextView TextViewObject;
    TextViewObject = activity.findViewById(R.id.TextView3id);
    TextViewObject.setText("Hard coded ID");

    //if I want to run a method from my main Class
    Bert dezeBert = new Bert();
    dezeBert.CalculateFromClass(activity);
    }
}
frans
  • 17
  • 3
0

This is kotlin code to access the view inside another layout :

//inflate layout
  val view = inflate(this, R.layout.ly_custom_menu_item, null)
//access view inside the inflated    
val tv = view.findViewById<AppCompatTextView>(R.id.txtV_WalletBalance_SideMenu)
//set value to view
 tv.text = "Value"
//Add inflated Layout to something
Hamed Jaliliani
  • 2,789
  • 24
  • 31
-1

you can do the following thing. I tried it and it worked. Just pass in the reference to the TextView while calling the method from another class. The problem in your version, is that there is conflict between TextView because you are declaring it twice. Instead declare it only once and pass it as an argument while calling the method in another class. Cheers!!

    // activity class


public class MainMenu extends Activity {
    public TextView txtView;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
        TextView txtView = (TextView)findViewById(R.id.text);
        ClassB.update(txtView);
    }    

}

    // other class



    public class ClassB {
       public ClassB(){
       }

       public void update(TextView tv){
            tv.setText("Hello");
       }
    }
hyperCoder
  • 271
  • 2
  • 13
-1

This can be manage easily in simple to steps.

================================

1) Activity to Multiple Fragment

below line can write in Fragment class Via FindViewById

((TextView) ((Activity) getActivity()).findViewById(R.id.textview)).setText("");

kirti
  • 183
  • 16