0

I have an Activity hosting 3 Fragments, my activity contains some data that I want my first fragment to use. I tried invoking the following method so that the activity can communicate with the fragment:

FirstFragment fragmentFirst = (FirstFragment)
                getSupportFragmentManager().findFragmentById(R.id.firstFragment);
        assert fragmentFirst != null;
        fragmentFirst.doSomething("some param");

For the activity class and:

public void doSomething(String param) {
        // do something in fragment
    }

For the fragment class, but it brought out an: Attempt to invoke virtual method 'void com.tex.lightweatherforecast.FirstFragment.doSomething(java.lang.String)' on a null object reference error at runtime.

My Activity code is:

public class HomeActivity extends AppCompatActivity {
    public static String BaseUrl = "http://api.openweathermap.org/";
    public static String AppId = "9c547bfc852923c3b30d0d62a5ae35e8";
    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);
        FirstFragment fragmentFirst = (FirstFragment)
                getSupportFragmentManager().findFragmentById(R.id.firstFragment);
        assert fragmentFirst != null;
        fragmentFirst.doSomething("some param");

        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(response.body().getCurrent().getTemp() + " ℃");
                    current_output.setText(response.body().getCurrent().getWeather().get(0).getDescription());
                    rise_time.setText(response.body().getCurrent().getSunrise() + " AM");
                    set_time.setText(response.body().getCurrent().getSunset() + " PM");
                    temp_out.setText(response.body().getCurrent().getTemp() + " ℃");
                    Press_out.setText(response.body().getCurrent().getPressure() + " hpa");
                    Humid_out.setText(response.body().getCurrent().getHumidity() + " %");
                    Ws_out.setText(response.body().getCurrent().getWindSpeed() + " Km/h");
                    Visi_out.setText(response.body().getCurrent().getVisibility() + " m");

                }
            }

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

Any help will be Appreciated

Chinez
  • 551
  • 2
  • 6
  • 29
  • Could you post import part in your activity? – Daniel.Wang Jan 06 '21 at 16:42
  • Have you considered using ViewModel to pass data between the Fragment and activity? – Shawn Jan 06 '21 at 17:21
  • @shawn no, I haven't, I don't know how – Chinez Jan 06 '21 at 17:36
  • 1
    ViewModel is designed to pass data between components and save data on configuration changes. It's is recommended by Android to handle your data. Android docs have instructions on how to set up a ViewModel. https://developer.android.com/topic/libraries/architecture/viewmodel? gclid=Cj0KCQiA3NX_BRDQARIsALA3fIK1sri8RQW5SEoB71sTbmQxcT4Y1E7HSv0U4dLI9Az2ZLUctQ4DnNsaAtM3EALw_wcB&gclsrc=aw.ds Android also provides code labs on view models https://developer.android.com/codelabs/kotlin-android-training-view-model#0 – Shawn Jan 06 '21 at 18:05
  • @shawn thanks, I'll try it out – Chinez Jan 07 '21 at 15:12

2 Answers2

0

If you want to pass activity data in your first destination fragment, avoid setting up graph in activity layout file.

Remove graph implementation from activity layout. And implement it as below in activity's onCreate method.

    NavController navController = Navigation.findNavController(this, R.id.nav_controller);
    Bundle bundle = new Bundle();
    bundle.putString("key", "value");
    navController.setGraph(navController.getGraph(), bundle);

Update your graph file by referring below code.

<navigation 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:id="@+id/navigation_graph"
        app:startDestination="@id/homeFragment">

    <fragment android:id="@+id/homeFragment"
          android:name="com.java.HomeFragment"
          android:label="home_fragment"
          tools:layout="@layout/fragment_home">

            <argument 
                android:name="key"
                app:argType="string"/>

    </fragment>

    ...
    ...

</navigation>

In your home fragment, get arguments value.

 String data = getArguments().getString("key");
DHAVAL A.
  • 2,251
  • 2
  • 12
  • 27
  • I have done everything you said, my homeactivity came with two errors: Cannot resolve method findNavController in HomeActivity and variable 'findNavController' is already defined. – Chinez Jan 06 '21 at 17:09
  • My Activity layout also: Element navigation is not allowed here and This namespace is redundant. Then it suggests me to safe delete 'data' – Chinez Jan 06 '21 at 17:10
  • I've updated code. Just remove your initialization of `navController`. And replace code there. And about the `data` variable. It is showing because you have initialized unused variable. So it is just lint. It'll disappear once you will use it. – DHAVAL A. Jan 06 '21 at 17:18
  • Android resource compilation failed(values.xml) – Chinez Jan 06 '21 at 17:33
0

I think you are getting this error because the fragment is not active. and if it's active and this approach worked, it's pretty bad approach, there are other ways in when we use the new LiveData and ViewModel arch style.

If you think that DataSource(LiveData usually) will be needed by children(Fragments) make this DataSource owned by the Parent (activity) and keep watching that DataSource inside the fragment(i.e. make observers of this LiveData inside different fragments)

If you need to make a callback from the Fragment to the activity use Contracts(Interfaces) and implement that contract inside the activity(this contract might contain functions like doSomething()) and call that from inside the fragment like this:

(((ContractName)activity).doSomething(passData))
Mohammad Elsayed
  • 1,885
  • 1
  • 20
  • 46
  • Yeah, I know my method is pretty bad and that's because I'm doing it for the first time. "If you need to make a callback from the Fragment to the activity use Contracts(Interfaces)" I really don't know how to implement it myself – Chinez Jan 06 '21 at 17:13
  • Look, you just a create an interface file anywhere call it anything ex ContractName normally and go to Activity and after the class MainActivity extends blablabla and before tye bracket write "extends Contract name" android studio will ask you to implement the methods written inside the interface and you can call that interface from fragment like I showed you in the answer above. – Mohammad Elsayed Jan 06 '21 at 22:50