Reputation: 593
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
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
Reputation: 758
You're getting a NullPointerException because findViewById(R.id.button2)
returns null.
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: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
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)
updateData()
in fragment class to update data to Text View.fragment
parameter in activity class and assign to this parameter when add fragment to activity.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
Reputation: 2515
There are many ways to achieve what you are tying to do, I will be using RxJava.
RxJava
in your project:implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
RxJava
buspublic 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;
}
}
import io.reactivex.rxjava3.subjects.BehaviorSubject;
public class RxJavaBus {
private static final BehaviorSubject<WeatherReport> behaviorSubject
= BehaviorSubject.create();
public static BehaviorSubject<WeatherReport> getSubject() {
return behaviorSubject;
}
}
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);
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
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