Chinez
Chinez

Reputation: 593

How to Call my Fragment's TextViews and Button from my Activity?

To start with, I'm still new to Android development, I've asked similar questions here before How to make my Fragment make use of my Activity Data? and How to send data from Activity to Fragment?(Android) But it seems that most people did not quite understand what I mean, so let me explain better. Suppose I have a TextView:

<TextView
            android:id="@+id/textView3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:text="@string/current"
            android:textColor="#FF3D19"
            android:textSize="24sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.498"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

and a Button:

<Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="14dp"
            android:layout_marginTop="92dp"
            android:shadowColor="#FFFFFF"
            android:text="Click"
            android:textColor="#FF5722"
            app:layout_constraintStart_toStartOf="@+id/imageView3"
            app:layout_constraintTop_toTopOf="@+id/imageView3" /> 

in my FirstFragment layout.xml, then i want to call them from my MainActivity i.e current_temp = findViewById(R.id.textView10); to get data i.e current_temp.setText(getString(R.string.blank, response.body().getCurrent().getTemp() + " ℃")); from a weather API and findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() {. Android Studio will not allow you to just use them like that, so i get this error:

java.lang.NullPointerException: Attempt to invoke virtual method 'void android.view.View.setOnClickListener(android.view.View$OnClickListener)' on a null object reference

whenever I run the app. I don't get any other error before running, so the reason is that I've not instantiated any method or maybe callback or interface that will tell the Activity to make use of my fragment's TextViews and Button. So that's what I've been stuck with for days trying to fix, but most people just misunderstood me and started suggesting I should learn ViewModel or LiveData. I watched several tutorials on ViewModel including Codinginflow, they never talked about linking activity to fragment TextViews, they only made a fragment send data to another fragment which is not what I want. My request is similar to this Android: Can't update textview in Fragment from Activity. NullPointerException tried and failed, So I need step by step procedures on how to apply it.

Full Code: HomeActivity

public class HomeActivity extends AppCompatActivity {
    public static String BaseUrl = "http://api.openweathermap.org/";
    public static String AppId = "";
    public static String lat = "9.0574";
    public static String lon = "7.4898";
    // User Timezone name, current time, current temperature, current condition, sunrise, sunset, temperature, pressure, humidity, wind_speed, visibility, UV Index
    TextView time_zone, time_field, current_temp, current_output, rise_time, set_time, temp_out, Press_out, Humid_out, Ws_out, Visi_out, UV_out;
    ConstraintLayout constraintLayout;
    public static int count = 0;
    int[] drawable = new int[]{R.drawable.dubai, R.drawable.central_bank_of_nigeria, R.drawable.eiffel_tower, R.drawable.hong_kong, R.drawable.statue_of_liberty};
    Timer _t;

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

        time_zone = findViewById(R.id.textView9);
        time_field = findViewById(R.id.textView4);
        current_temp = findViewById(R.id.textView10);
        current_output = findViewById(R.id.textView11);
        rise_time = findViewById(R.id.textView25);
        set_time = findViewById(R.id.textView26);
        temp_out = findViewById(R.id.textView28);
        Press_out = findViewById(R.id.textView29);
        Humid_out = findViewById(R.id.textView30);
        Ws_out = findViewById(R.id.textView33);
        Visi_out = findViewById(R.id.textView34);
        UV_out = findViewById(R.id.textView35);

        BottomNavigationView bottomNavigationView = findViewById(R.id.bottomNavigationView);
        NavController navController = Navigation.findNavController(this, R.id.fragment);
        NavigationUI.setupWithNavController(bottomNavigationView, navController);

        findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                getCurrentData();
                constraintLayout = findViewById(R.id.layout);
                constraintLayout.setBackgroundResource(R.drawable.dubai);
                _t = new Timer();
                _t.scheduleAtFixedRate(new TimerTask() {
                    @Override
                    public void run() {
                        runOnUiThread(new Runnable() { // run on ui thread
                            @Override
                            public void run() {
                                if (count < drawable.length) {

                                    constraintLayout.setBackgroundResource(drawable[count]);
                                    count = (count + 1) % drawable.length;
                                }
                            }
                        });
                    }
                }, 5000, 5000);
            }

            void getCurrentData() {
                Retrofit retrofit = new Retrofit.Builder().baseUrl(BaseUrl).addConverterFactory(GsonConverterFactory.create()).build();
                WeatherService service = retrofit.create(WeatherService.class);
                Call<WeatherResponse> call = service.getCurrentWeatherData(lat, lon, AppId);
                call.enqueue(new Callback<WeatherResponse>() {
                    @Override
                    public void onResponse(@NonNull Call<WeatherResponse> call, @NonNull Response<WeatherResponse> response) {
                        if (response.code() == 200) {
                            WeatherResponse weatherResponse = response.body();
                            assert weatherResponse != null;

                            assert response.body() != null;
                            time_zone.setText(response.body().getTimezone());
                            time_field.setText(response.body().getCurrent().getDt());
                            current_temp.setText(getString(R.string.blank, response.body().getCurrent().getTemp() + " ℃"));
                            current_output.setText(response.body().getCurrent().getWeather().get(0).getDescription());
                            rise_time.setText(getString(R.string.blank, response.body().getCurrent().getSunrise() + " AM"));
                            set_time.setText(getString(R.string.blank, response.body().getCurrent().getSunset() + " PM"));
                            temp_out.setText(getString(R.string.blank, response.body().getCurrent().getTemp() + " ℃"));
                            Press_out.setText(getString(R.string.blank, response.body().getCurrent().getPressure() + " hpa"));
                            Humid_out.setText(getString(R.string.blank, response.body().getCurrent().getHumidity() + " %"));
                            Ws_out.setText(getString(R.string.blank, response.body().getCurrent().getWindSpeed() + " Km/h"));
                            Visi_out.setText(getString(R.string.blank, response.body().getCurrent().getVisibility() + " m"));
                        }
                    }

                    @Override
                    public void onFailure(@NonNull Call<WeatherResponse> call, @NonNull Throwable t) {
                    }
                });
            }
        });
    }
}

FirstFragment

public class FirstFragment extends Fragment {
// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private static final String ARG_PARAM1 = "param1";
private static final String ARG_PARAM2 = "param2";

// TODO: Rename and change types of parameters
private String mParam1;
private String mParam2;

public FirstFragment() {
    // Required empty public constructor
}

/**
 * Use this factory method to create a new instance of
 * this fragment using the provided parameters.
 *
 * @param param1 Parameter 1.
 * @param param2 Parameter 2.
 * @return A new instance of fragment SecondFragment.
 */
// TODO: Rename and change types and number of parameters
public static FirstFragment newInstance(String param1, String param2) {
    FirstFragment fragment = new FirstFragment();
    Bundle args = new Bundle();
    args.putString(ARG_PARAM1, param1);
    args.putString(ARG_PARAM2, param2);
    fragment.setArguments(args);
    return fragment;
}

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (getArguments() != null) {
        mParam1 = getArguments().getString(ARG_PARAM1);
        mParam2 = getArguments().getString(ARG_PARAM2);
    }
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_first, container, false);
}
}

Taking the whole functionality to Fragment won't work too, because retrofit can only be called from Activity. But if you have any solid suggestions, I'll appreciate it.

Upvotes: 0

Views: 1236

Answers (5)

Umair Saeed
Umair Saeed

Reputation: 86

Create a class to get retrofit client like below

 class RetrofitClient {
    private Retrofit retrofit;
    private String BASE_URL = "";

    public ServiceAPI getRetrofitInstance() {
        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(BASE_URL)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit.create(WeatherService.class);
   }

Now you can call apis inside fragment like this

WeatherService service = RetrofitClient().getRetrofitInstance()
    
    void getCurrentData() {
                  
                    Call<WeatherResponse> call = service.getCurrentWeatherData(lat, lon, AppId);
                    call.enqueue(new Callback<WeatherResponse>() {
                        @Override
                        public void onResponse(@NonNull Call<WeatherResponse> call, @NonNull Response<WeatherResponse> response) {
                            if (response.code() == 200) {
                                WeatherResponse weatherResponse = response.body();
                                assert weatherResponse != null;
    
                                assert response.body() != null;
                                time_zone.setText(response.body().getTimezone());
                                time_field.setText(response.body().getCurrent().getDt());
                                current_temp.setText(getString(R.string.blank, response.body().getCurrent().getTemp() + " ℃"));
                                current_output.setText(response.body().getCurrent().getWeather().get(0).getDescription());
                                rise_time.setText(getString(R.string.blank, response.body().getCurrent().getSunrise() + " AM"));
                                set_time.setText(getString(R.string.blank, response.body().getCurrent().getSunset() + " PM"));
                                temp_out.setText(getString(R.string.blank, response.body().getCurrent().getTemp() + " ℃"));
                                Press_out.setText(getString(R.string.blank, response.body().getCurrent().getPressure() + " hpa"));
                                Humid_out.setText(getString(R.string.blank, response.body().getCurrent().getHumidity() + " %"));
                                Ws_out.setText(getString(R.string.blank, response.body().getCurrent().getWindSpeed() + " Km/h"));
                                Visi_out.setText(getString(R.string.blank, response.body().getCurrent().getVisibility() + " m"));
                            }
                        }
    
                        @Override
                        public void onFailure(@NonNull Call<WeatherResponse> call, @NonNull Throwable t) {
                        }
                    });
                }
            });
        }

Upvotes: 1

user3252344
user3252344

Reputation: 758

You're getting a NullPointerException because findViewById(R.id.button2) returns null.

  • You need to use the fragment's view as the root. The activity.findViewById(...) call uses the activity's view as the root - it's searching activity_home.xml, which doesn't have button2. You can access the fragment's version with fragment.getView().findViewById(R.id._), BUT:
  • You need to make sure that the fragment has at finished its onCreateView() step in the life cycle. So it has to be launched and inflated. Otherwise, again, the button doesn't exist.

You can make sure your setup is called at the right time and with the view by making it a callback in the fragment life cycle.

//This class can be anywhere - it can be global, private inner, anonymous
class SetupCallback extends FragmentLifeCycleCallback {
    @Override
    void onFragmentViewCreated(FragmentManager fm, Fragment f, View v, Bundle savedInstanceState) {
        ButtonView button = v.findViewById(R.id.button2);
        if(button != null) {    //This gets called for all fragments so check for null
            button.setOnClickListener(new OnClickListener {...});

        }

        //Repeat for any other elements in the view e.g.
        TextView someText = v.findViewById(R.id.someTextView);
        someText.setText(R.string.message);
    }
}

//In the activity setup - I usually put in onCreate()
SupportFragmentManager.supportFragmentManager = getSupportFragmentManager();
supportFragmentManager.registerFragmentLifecycleCallbacks(new SetupCallback());
//anonymous version:
//supportFragmentManager.registerFragmentLifecycleCallbacks(new FragmentLifecycleCallback() { /*contents of class*/ });

Alternatively you could possibly initialise the Retrofit and Weather service in the Activity, pass them in to the fragment with the constructor or a bundle, then use them to setup the onClickListener in onCreateView().

The RxJava and SharedView approaches are alternatives to having a listener stuck on all the fragment life cycle calls, vs having alternative architectures which make the fragment more reactive.

This needs the androidx.lifecycle.common dependency. If you're using gradle that would be:

dependencies {
    implementation 'androidx.lifecycle:lifecycle-common:2.3.0-rc1'
}

Upvotes: 1

Tungken
Tungken

Reputation: 1985

I suggest you two ways can update data to Text View of Fragment from Activity.

1. Call method update Text View of fragment from Activity (simple way)

  • Create a method updateData()in fragment class to update data to Text View.
  • You declare a fragment parameter in activity class and assign to this parameter when add fragment to activity.
  • When you receive data from api in activity, call fragment.updateData()

fragment_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/temp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:text="TextView"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/hud"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="16dp"
        android:text="TextView"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/temp" />

    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

home_activity.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

FirstFragment.kt

class FirstFragment : Fragment(R.layout.fragment_layout) {

    private lateinit var temp: TextView
    private lateinit var hud: TextView

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        temp = view.findViewById(R.id.temp)
        hud = view.findViewById(R.id.hud)
    }

    fun setDataToView(data: WeatherResponse) {
        temp.text = data.temp
        hud.text = data.hud
    }
}

HomeActivity.kt

class HomeActivity: AppCompatActivity() {

    private val fragment = FirstFragment()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.home_activity)

        addFragment()

        fragment.view?.findViewById<Button>(R.id.button2)?.setOnClickListener {
            getCurrentData()
        }


    }

    private fun addFragment(){
        val fm = supportFragmentManager
        fm.beginTransaction().replace(R.id.container, fragment, "FirstFragment").commit()
    }

    private fun getCurrentData(){
        //Your retrofit code in here. I only show code in onResponse()
        //.....
        @Override
        public void onResponse(@NonNull Call<WeatherResponse> call, @NonNull Response<WeatherResponse> response) {
            if (response.code() == 200) {
                fragment.setDataToView(response)
            }
        }

        //....
    }
}

2. Using ViewModel

  • Create a SharedViewModel class with a livedata parameter.

  • In activity, on onCreate() you create a SharedViewModel paramerter like below:

    SharedViewModel viewModel = new SharedViewModel(this).get(SharedViewModel .class);

  • In fragment, on onActivityCreated() you create a SharedViewModel paramerter like below:

    SharedViewModel viewModel = new SharedViewModel(requireActivity()).get(SharedViewModel .class);

  • Finally, you have same ViewModel instance in activity and fragment because both using activity context. When you receive data from api, update your livedata parameter in activity, fragment also receives livedata parameter onChanged event and then you can update Text View.

Upvotes: 2

Mustansir
Mustansir

Reputation: 2515

There are many ways to achieve what you are tying to do, I will be using RxJava.

  1. Add RxJava in your project:

implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'

  1. Create a POJO class to hold your data which then later can be transferred using RxJava bus
public class WeatherReport {

    private String time_zone;
    private String time_field;
    private String current_temp;
    private String current_output;
    private String rise_time;
    private String set_time;
    private String temp_out;
    private String Press_out;
    private String Humid_out;
    private String Ws_out;
    private String Visi_out;

    public String getCurrent_output() {
        return current_output;
    }

    public String getCurrent_temp() {
        return current_temp;
    }

    public String getHumid_out() {
        return Humid_out;
    }

    public String getPress_out() {
        return Press_out;
    }

    public String getRise_time() {
        return rise_time;
    }

    public String getSet_time() {
        return set_time;
    }

    public String getTemp_out() {
        return temp_out;
    }

    public String getTime_field() {
        return time_field;
    }

    public String getTime_zone() {
        return time_zone;
    }

    public String getVisi_out() {
        return Visi_out;
    }

    public String getWs_out() {
        return Ws_out;
    }

    public void setCurrent_output(String current_output) {
        this.current_output = current_output;
    }

    public void setCurrent_temp(String current_temp) {
        this.current_temp = current_temp;
    }

    public void setHumid_out(String humid_out) {
        Humid_out = humid_out;
    }

    public void setPress_out(String press_out) {
        Press_out = press_out;
    }

    public void setRise_time(String rise_time) {
        this.rise_time = rise_time;
    }

    public void setSet_time(String set_time) {
        this.set_time = set_time;
    }

    public void setTemp_out(String temp_out) {
        this.temp_out = temp_out;
    }

    public void setTime_field(String time_field) {
        this.time_field = time_field;
    }

    public void setTime_zone(String time_zone) {
        this.time_zone = time_zone;
    }

    public void setVisi_out(String visi_out) {
        Visi_out = visi_out;
    }

    public void setWs_out(String ws_out) {
        Ws_out = ws_out;
    }
}
  1. Create a RxJava bus
import io.reactivex.rxjava3.subjects.BehaviorSubject;

public class RxJavaBus {

    private static final BehaviorSubject<WeatherReport> behaviorSubject
            = BehaviorSubject.create();


    public static BehaviorSubject<WeatherReport> getSubject() {
        return behaviorSubject;
    }

}
  1. Now from your activity(or from wherever you want to transfer data) use RxBus like this:
WeatherReport weatherReport = new WeatherReport();
        
weatherReport.setCurrent_output("Some DATA");
weatherReport.setCurrent_temp("Some DATA");
weatherReport.setHumid_out("Some DATA");
weatherReport.setPress_out("Some DATA");
weatherReport.setRise_time("Some DATA");
weatherReport.setSet_time("Some DATA");
weatherReport.setTime_field("Some DATA");
weatherReport.setVisi_out("Some DATA");
weatherReport.setTemp_out("Some DATA");
weatherReport.setWs_out("Some DATA");
weatherReport.setTime_zone("some DATA");

RxJavaBus.getSubject().onNext(weatherReport);
  1. Now you receive your data in your fragment (or wherever you want) like this:
RxJavaBus.getSubject().subscribe(weatherReportFromActivity -> {

            weatherReportFromActivity.getCurrent_output();
            weatherReportFromActivity.getCurrent_temp();
            weatherReportFromActivity.getHumid_out();
            weatherReportFromActivity.getPress_out();
            weatherReportFromActivity.getRise_time();
            weatherReportFromActivity.getSet_time();
            weatherReportFromActivity.getWs_out();
            weatherReportFromActivity.getTime_zone();
            weatherReportFromActivity.getVisi_out();
            weatherReportFromActivity.getTime_field();
            weatherReportFromActivity.getTemp_out();
});

Upvotes: 1

End User
End User

Reputation: 812

Set the data you want to update in the bundle set fragment arguments and call the newInstance then update your views accordingly.

From Activity

   FirstFragment.newInstance("yourData","yourData");

In your Fragment

if (getArguments() != null) {
        mParam1 = getArguments().getString(ARG_PARAM1);
        mParam2 = getArguments().getString(ARG_PARAM2);
    }

Upvotes: 0

Related Questions