Data resets when bottom navigation views are clicked - java

I have a bottom navigation view on my weather app containing 3 panels(Today, hourly & Daily). My activity hosts 3 fragments for the 3 panels. Currently, if I search for any city on the today fragment, it gives the data for such cities. Now the problem is that if I click on any bottom nav view(down), it resets the data displayed on the today fragment.
Here is an illustration of the issue:
Data is displayed after a city is searched(the part with a red tick):
Data goes on reset after clicking these bottom nav views(the part with a red tick):
I want the data to remain intact irrespective of clicking those nav views.
I tried using https://stackoverflow.com/a/60201555/16020235 suggestion. But it failed with this
exception:
java.lang.IllegalArgumentException: No view found for id 0x7f0a0116 (com.viz.lightweatherforecast:id/my_nav) for fragment ThirdFragment{90bc0de} (8e129d17-010d-41dc-9311-82e273b4e522 id=0x7f0a0116 tag=3)
at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:513)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:282)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2189)
at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2100)
at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:2002)
at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3134)
at androidx.fragment.app.FragmentManager.dispatchActivityCreated(FragmentManager.java:3068)
at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:251)
at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:501)
at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:210)
at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1391)
at android.app.Activity.performStart(Activity.java:7157)
at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3037)
at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:180)
at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:165)
at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:142)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:70)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1861)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6819)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:497)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:912)
I/weatherforecas: Compiler allocated 4MB to compile void android.widget.TextView.<init>(android.content.Context, android.util.AttributeSet, int, int)
I find it hard to implement his code and the remaining suggestions on that question are written in kotlin.
Please how can I resolve this?
Here are my codes:
my_nav.xml(navigation layout):
<?xml version="1.0" encoding="utf-8"?>
<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/my_nav"
app:startDestination="#id/firstFragment">
<fragment
android:id="#+id/firstFragment"
android:name="com.viz.lightweatherforecast.FirstFragment"
android:label="fragment_first"
tools:layout="#layout/fragment_first" />
<fragment
android:id="#+id/secondFragment"
android:name="com.viz.lightweatherforecast.SecondFragment"
android:label="fragment_second"
tools:layout="#layout/fragment_second" />
<fragment
android:id="#+id/thirdFragment"
android:name="com.viz.lightweatherforecast.ThirdFragment"
android:label="fragment_third"
tools:layout="#layout/fragment_third" />
</navigation>
activity_home.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"
android:id="#+id/layout"
android:background="#drawable/dubai"
tools:context=".Activity.HomeActivity">
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/bottomNavigationView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#FFFFFF"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="#menu/bottom_menu" />
<androidx.fragment.app.FragmentContainerView
android:id="#+id/fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="409dp"
android:layout_height="599dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toTopOf="#+id/bottomNavigationView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:navGraph="#navigation/my_nav"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
HomeActivity:
public class HomeActivity extends AppCompatActivity {
// Last update time, click sound, search button, search panel.
TextView time_field;
MediaPlayer player;
ImageView Search;
EditText textfield;
// For scheduling background image change(using constraint layout, start counting from dubai, down to statue of liberty.
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);
// use home activity layout.
time_field = findViewById(R.id.textView9);
Search = findViewById(R.id.imageView4);
textfield = findViewById(R.id.textfield);
// find the id's of specific variables.
BottomNavigationView bottomNavigationView = findViewById(R.id.bottomNavigationView);
// host 3 fragments along with bottom navigation.
final NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager().findFragmentById(R.id.fragment);
assert navHostFragment != null;
final NavController navController = navHostFragment.getNavController();
NavigationUI.setupWithNavController(bottomNavigationView, navController);
// For scheduling background image change
constraintLayout = findViewById(R.id.layout);
constraintLayout.setBackgroundResource(R.drawable.dubai);
_t = new Timer();
_t.scheduleAtFixedRate(new TimerTask() {
#Override
public void run() {
// run on ui thread
runOnUiThread(() -> {
if (count < drawable.length) {
constraintLayout.setBackgroundResource(drawable[count]);
count = (count + 1) % drawable.length;
}
});
}
}, 5000, 5000);
Search.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// make click sound when search button is clicked.
player = MediaPlayer.create(HomeActivity.this, R.raw.click);
player.start();
getWeatherData(textfield.getText().toString().trim());
// make use of some fragment's data
FirstFragment firstFragment = (FirstFragment) navHostFragment.getChildFragmentManager().getFragments().get(0);
firstFragment.getWeatherData(textfield.getText().toString().trim());
}
});
}
});
}
}
EDIT
Fragment class:
public class FirstFragment extends Fragment {
// User current time, current temperature, current condition, sunrise, sunset, temperature, pressure, humidity, wind_speed, visibility, clouds
TextView current_temp, current_output, rise_time, set_time, temp_out, Press_out, Humid_out, Ws_out, Visi_out, Cloud_out;
// 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
View rootView = inflater.inflate(R.layout.fragment_first, container, false);
// For displaying weather data
current_temp = rootView.findViewById(R.id.textView10);
current_output = rootView.findViewById(R.id.textView11);
rise_time = rootView.findViewById(R.id.textView25);
set_time = rootView.findViewById(R.id.textView26);
temp_out = rootView.findViewById(R.id.textView28);
Press_out = rootView.findViewById(R.id.textView29);
Humid_out = rootView.findViewById(R.id.textView30);
Ws_out = rootView.findViewById(R.id.textView33);
Visi_out = rootView.findViewById(R.id.textView34);
Cloud_out = rootView.findViewById(R.id.textView35);
return rootView;
}
public void getWeatherData(String name) {
ApiInterface apiInterface = ApiClient.getClient().create(ApiInterface.class);
Call<Example> call = apiInterface.getWeatherData(name);
call.enqueue(new Callback<Example>() {
#Override
public void onResponse(#NonNull Call<Example> call, #NonNull Response<Example> response) {
try {
assert response.body() != null;
current_temp.setVisibility(View.VISIBLE);
current_temp.setText(response.body().getMain().getTemp() + " ℃");
current_output.setVisibility(View.VISIBLE);
current_output.setText(response.body().getWeather().get(0).getDescription());
rise_time.setVisibility(View.VISIBLE);
rise_time.setText(response.body().getSys().getSunrise() + " ");
set_time.setVisibility(View.VISIBLE);
set_time.setText(response.body().getSys().getSunset() + " ");
temp_out.setVisibility(View.VISIBLE);
temp_out.setText(response.body().getMain().getTemp() + " ℃");
Press_out.setVisibility(View.VISIBLE);
Press_out.setText(response.body().getMain().getPressure() + " hpa");
Humid_out.setVisibility(View.VISIBLE);
Humid_out.setText(response.body().getMain().getHumidity() + " %");
Ws_out.setVisibility(View.VISIBLE);
Ws_out.setText(response.body().getWind().getSpeed() + " Km/h");
Visi_out.setVisibility(View.VISIBLE);
Visi_out.setText(response.body().getVisibility() + " m");
Cloud_out.setVisibility(View.VISIBLE);
Cloud_out.setText(response.body().getClouds().getAll() + " %");
} catch (Exception e) {
Log.e("TAG", "No City found");
current_temp.setVisibility(View.GONE);
current_output.setVisibility(View.GONE);
rise_time.setVisibility(View.GONE);
set_time.setVisibility(View.GONE);
temp_out.setVisibility(View.GONE);
Press_out.setVisibility(View.GONE);
Humid_out.setVisibility(View.GONE);
Ws_out.setVisibility(View.GONE);
Visi_out.setVisibility(View.GONE);
Cloud_out.setVisibility(View.GONE);
Toast.makeText(getActivity(), "No City found", Toast.LENGTH_SHORT).show();
}
}
#Override
public void onFailure(#NotNull Call<Example> call, #NotNull Throwable t) {
t.printStackTrace();
}
});
}
}
I didn't paste everything because I'm following https://stackoverflow.com/help/minimal-reproducible-example

Your problem is that your FirstFragment is not saving its state properly. As per the Saving state with fragments guide:
To ensure the user's state is saved, the Android framework automatically saves and restores the fragments and the back stack. Therefore, you need to ensure that any data in your fragment is saved and restored as well.
But you aren't saving the last name that you pass to getWeatherData, nor are you saving your Example object that you get from your API call in order to repopulate your views when they are recreated.
So need to actually use the APIs described in that guide to save your state. Namely, you should file the Guide to app architecture, which explains how you can separate your from your data loading by using ViewModels to store data across configuration changes (like rotating your device) and LiveData to automatically populate your UI whenever your data is loaded.
The first thing we want to do is move the data loading to a ViewModel. This object survives configuration changes which means any data stored in this class is automatically saved when you rotate your device. This is how we can save your Example class and avoid calling the server over and over.
By using the APIs in the Saved State module for ViewModel (specifically, the SavedStateHandle class), any data you save in there will survive your process being killed and later recreated (say, if your device is low on memory, etc.). This is how we can save the last name so that we will automatically requery for your data.
Here, our ViewModel handles all of the loading from the server and uses a LiveData to let our UI automatically update as the data is loaded.
public class WeatherDataViewModel extends ViewModel {
// This will save the city name
private SavedStateHandle state;
// This is where we'll store our result from the server
private MutableLiveData<Example> mutableWeatherData = new MutableLiveData<>();
public WeatherDataViewModel(SavedStateHandle savedStateHandle) {
state = savedStateHandle;
String savedCityName = state.get("name");
if (savedCityName != null) {
// We already had a previously saved name, so we'll
// start loading right away
loadData();
}
}
// This is what our Fragment will use to get the latest weather data
public LiveData<Example> getWeatherDataLiveData() {
return mutableWeatherData;
}
// When you get a new city name, we'll save that in our
// state, then load the new data from the server
public void setCityName(String name) {
state.set("name", name);
loadData();
}
private void loadData() {
// Get the last name that was set
String name = state.get("name");
// Now kick off a load from the server
ApiInterface apiInterface = ApiClient.getClient().create(ApiInterface.class);
Call<Example> call = apiInterface.getWeatherData(name);
call.enqueue(new Callback<Example>() {
#Override
public void onResponse(#NonNull Call<Example> call, #NonNull Response<Example> response) {
// Save the response we've gotten
// This will automatically update our UI
mutableWeatherData.setValue(response.body());
}
#Override
public void onFailure(#NotNull Call<Example> call, #NotNull Throwable t) {
t.printStackTrace();
}
});
}
}
Now you can rewrite your FirstFragment to use the WeatherDataViewModel as the source of truth for your UI:
public class FirstFragment extends Fragment {
private WeatherDataViewModel viewModel;
public FirstFragment() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.fragment_first, container, false);
// For displaying weather data
final TextView current_temp = rootView.findViewById(R.id.textView10);
final TextView current_output = rootView.findViewById(R.id.textView11);
final TextView rise_time = rootView.findViewById(R.id.textView25);
final TextView set_time = rootView.findViewById(R.id.textView26);
final TextView temp_out = rootView.findViewById(R.id.textView28);
final TextView Press_out = rootView.findViewById(R.id.textView29);
final TextView Humid_out = rootView.findViewById(R.id.textView30);
final TextView Ws_out = rootView.findViewById(R.id.textView33);
final TextView Visi_out = rootView.findViewById(R.id.textView34);
final TextView Cloud_out = rootView.findViewById(R.id.textView35);
// Get our ViewModel instance
viewModel = new ViewModelProvider(this).get(WeatherDataViewModel.class);
// And whenever the data changes, refresh the UI
viewModel.getWeatherDataLiveData().observe(getViewLifecycleOwner(), data -> {
if (data != null) {
current_temp.setVisibility(View.VISIBLE);
current_temp.setText(data.getMain().getTemp() + " ℃");
current_output.setVisibility(View.VISIBLE);
current_output.setText(data.getWeather().get(0).getDescription());
rise_time.setVisibility(View.VISIBLE);
rise_time.setText(data.getSys().getSunrise() + " ");
set_time.setVisibility(View.VISIBLE);
set_time.setText(data.getSys().getSunset() + " ");
temp_out.setVisibility(View.VISIBLE);
temp_out.setText(data.getMain().getTemp() + " ℃");
Press_out.setVisibility(View.VISIBLE);
Press_out.setText(data.getMain().getPressure() + " hpa");
Humid_out.setVisibility(View.VISIBLE);
Humid_out.setText(data.getMain().getHumidity() + " %");
Ws_out.setVisibility(View.VISIBLE);
Ws_out.setText(data.getWind().getSpeed() + " Km/h");
Visi_out.setVisibility(View.VISIBLE);
Visi_out.setText(data.getVisibility() + " m");
Cloud_out.setVisibility(View.VISIBLE);
Cloud_out.setText(data.getClouds().getAll() + " %");
} else {
Log.e("TAG", "No City found");
current_temp.setVisibility(View.GONE);
current_output.setVisibility(View.GONE);
rise_time.setVisibility(View.GONE);
set_time.setVisibility(View.GONE);
temp_out.setVisibility(View.GONE);
Press_out.setVisibility(View.GONE);
Humid_out.setVisibility(View.GONE);
Ws_out.setVisibility(View.GONE);
Visi_out.setVisibility(View.GONE);
Cloud_out.setVisibility(View.GONE);
Toast.makeText(requireActivity(), "No City found", Toast.LENGTH_SHORT).show();
}
});
return rootView;
}
public void getWeatherData(String name) {
// The ViewModel controls loading the data, so we just
// tell it what the new name is - this kicks off loading
// the data, which will automatically call through to
// our observe() call when the data load completes
viewModel.setCityName(name);
}
}
With these changes, you'll find that your Fragment now correctly handles:
Being put on the Fragment back stack
Configuration changes (i.e., rotating your device)
Process death and recreation (i.e., testing with 'Don't keep activities' on)
You'll note how we use new ViewModelProvider(this).get(WeatherDataViewModel.class) - that creates a WeatherDataViewModel that is tied to this - your Fragment itself. This is best if the data your ViewModel loads is only used in that one Fragment.
If you also wanted to use this same data in your Activity, your activity could use new ViewModelProvider(this).get(WeatherDataViewModel.class) to create a WeatherDataViewModel that is scoped to the entire Activity. Any Fragment could then use new ViewModelProvider(requireActivity()).get(WeatherDataViewModel.class) to get that Activity owned ViewModel. This would potentially mean that you wouldn't need a getWeatherData() method on your Fragment at all - instead, your Activity would directly call viewModel.setCityName(name) itself and all Fragments would just instantly update (as they read from the same ViewModel).

I think, this is because your FirstFragment gets reinstantiated every time. To save state, modify onCreate() of your FirstFragment like following:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {
mParam1 = savedInstanceState.getString(ARG_PARAM1);
mParam2 = savedInstanceState.getString(ARG_PARAM2);
} else if (getArguments() != null) {
mParam1 = getArguments().getString(ARG_PARAM1);
mParam2 = getArguments().getString(ARG_PARAM2);
}
}
And add onSaveInstanceState() to your FirstFragment:
#Override
public void onSaveInstanceState(#NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putString(ARG_PARAM1, mParam1);
outState.putString(ARG_PARAM2, mParam2);
}

The mistake seems to be that every time you select an item in the bottom navigation bar the fragment gets re-instantized thus creating a new fragment(of same type, but a new object of that fragment).
I would suggest you create an object of all the fragment at the start like this
private final Fragment[] fragments = {
new HomeFragment(),
new MyNotesFragment(),
new PreferencesFragment()
};
and then for the Bottom Nav Listener part, we will change the fragments according to the button clicked by passing already made instances of fragments rather than creating new ones.
bottom_navigation_view.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
#Override
public boolean onNavigationItemSelected(#NonNull MenuItem item) {
// Checking which bottom_nav items is clicked by comparing with string names..
if (item.getTitle().toString().equals("Home")) {
// Code to change the fragments
final FragmentTransaction ft =
getFragmentManager().beginTransaction();
// we passed the already made instance of the home fragment rather than creating a new one
ft.replace(R.id.my_fragment, fragments[0], "HomeFrag");
ft.commit();
} else if (item.getTitle().toString().equals("My Notes")) {
// Code to change the fragments
final FragmentTransaction ft =
getFragmentManager().beginTransaction();
ft.replace(R.id.my_fragment, fragments[1], "MyNotesFrag");
ft.commit();
} else if (item.getTitle().toString().equals("Preferences")) {
// Code to change the fragments
final FragmentTransaction ft =
getFragmentManager().beginTransaction();
ft.replace(R.id.my_fragment, fragments[2], "PrefFrag");
ft.commit();
}
return true;
}
});
(Though this is for the ViewPager library the concept works the same)

If you still have not solve it yet, you might want to try this solution that uses SharedPreferences.
public class FirstFragment extends Fragment
{
// ...
private SharedPreferences prefs;
private SharedPreferences.Editor prefsEditor;
// ...
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.fragment_first, container, false);
// For displaying weather data
current_temp = rootView.findViewById(R.id.textView10);
current_output = rootView.findViewById(R.id.textView11);
// ...
prefs = new SharedPreferences(getActivity().getApplicationContext());
prefsEditor = prefs.edit();
current_temp.setText(prefs.getString("current_temp", "not yet initialised"));
current_output.setText(prefs.getString("current_output", "not yet initialised"));
// ...
return rootView;
}
public void getWeatherData(String name)
{
ApiInterface apiInterface = ApiClient.getClient().create(ApiInterface.class);
Call<Example> call = apiInterface.getWeatherData(name);
call.enqueue(new Callback<Example>() {
#Override
public void onResponse(#NonNull Call<Example> call, #NonNull Response<Example> response) {
try {
assert response.body() != null;
final String currentTemp = response.body().getMain().getTemp() + " ℃";
editor.putString("current_temp", currentTemp);
editor.commit();
current_temp.setText(currentTemp);
current_temp.setVisibility(View.VISIBLE);
final String currentOutput = response.body().getWeather().get(0).getDescription();
editor.putString("current_output", currentOutput);
editor.commit();
current_output.setText(currentOutput);
current_output.setVisibility(View.VISIBLE);
// ...
} catch (Exception e) {
Log.e("TAG", "No City found");
current_temp.setVisibility(View.GONE);
current_output.setVisibility(View.GONE);
// ...
Toast.makeText(getActivity(), "No City found", Toast.LENGTH_SHORT).show();
}
}
// ...
});
}
}

Related

Android app - Google maps location markers disappearing from Map fragment

I am very new to Android development. This is my first attempt at creating an app. I have created a tabbed navigation app, with 4 fragments to navigate between: Home, Search, Map and Account fragments from the MainActivity. I have used a SupportMapFragment within my MapsFragment to display the map. Upon visiting the MapsFragment the map displays and zooms in on my Current location and highlights certain important locations, retrieved from a Firebase instance, with markers.
Everything works as expected when I first launch the app and visit the Map Fragment. I am zoomed into my current location and I see all required markers. The problem arises when I switch to a different fragment from that point. When I navigate to the Home, Search or Account fragment and then return to the Map fragment, none of the location markers are visible anymore. I can still see my current location and get zoomed into it on opening the Map fragment but the other markers disappear.
I originally thought it was to do with the fragment being recreated everytime I navigate to another fragment as I use .replace(container, fragment) to change the fragment based on which button was clicked from the bottom navigation bar. But I observed that the Map fragment would load my current location and zoom into it correctly, so the loadMap() function gets executed but somehow the markers don't appear. If i restart the app, the markers behave correctly the first time I open the Map fragment.
Any help on how to keep the markers showing on the Map when I return to it after navigating to other fragments will be greatly appreciated. Thank you!
Here is a preview of my MapsFragment.java:
public class MapsFragment extends Fragment {
private SupportMapFragment supportMapFragment;
private AutocompleteSupportFragment autocompleteSupportFragment;
private FusedLocationProviderClient client;
private Geocoder geocoder;
private ArrayList<String> mPostCodes;
private ArrayList<Marker> searchMarker;
private DatabaseReference locationRef;
private DatabaseReference businessRef;
private String apiKey;
public MapsFragment() {
// Required empty public constructor
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Initialize Map fragment
supportMapFragment = (SupportMapFragment)
getChildFragmentManager().findFragmentById(R.id.google_map);
// initialize Places client
apiKey = getString(R.string.map_key);
if(!Places.isInitialized()){
Places.initialize(requireActivity(), apiKey);
}
PlacesClient placesClient = Places.createClient(requireActivity());
// Initialize AutoComplete search bar and set autocomplete parameters
autocompleteSupportFragment = (AutocompleteSupportFragment)
getChildFragmentManager().findFragmentById(R.id.autocomplete_fragment);
autocompleteSupportFragment.setTypeFilter(TypeFilter.ADDRESS);
autocompleteSupportFragment.setLocationBias(RectangularBounds.newInstance(
new LatLng(55.836229, -4.252612),
new LatLng(55.897463, -4.325364)));
autocompleteSupportFragment.setCountries("UK");
autocompleteSupportFragment.setPlaceFields(Arrays.asList(Place.Field.ID, Place.Field.NAME, Place.Field.LAT_LNG));
// initialize search marker list
searchMarker = new ArrayList<Marker>();
// Initialize client to get user's last location on device
client = LocationServices.getFusedLocationProviderClient(requireActivity());
// Initialize geocoder to convert business postcodes to latlng coordinates
mPostCodes = new ArrayList<>();
geocoder = new Geocoder(requireActivity());
// Get business locations
locationRef = FirebaseDatabase.getInstance().getReference("Business Locations");
locationRef.addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
if(snapshot.exists()){
for(DataSnapshot locationSnapshot : snapshot.getChildren()){
BusinessLocation location = locationSnapshot.getValue(BusinessLocation.class);
mPostCodes.add(location.getPostCode());
}
}
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
Log.i("Location error", "Error retrieving location: ", error.toException().getCause());
}
});
// initialize reference to business table
businessRef = FirebaseDatabase.getInstance().getReference("Businesses");
// render the map
loadMap();
}
private void loadMap() {
if (ActivityCompat.checkSelfPermission(requireActivity(),
Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
Task<Location> task = client.getLastLocation();
task.addOnSuccessListener(new OnSuccessListener<Location>() {
#Override
public void onSuccess(Location location) {
if(location != null){
supportMapFragment.getMapAsync(new OnMapReadyCallback() {
#Override
public void onMapReady(#NonNull GoogleMap googleMap) {
// autocomplete place search
autocompleteSupportFragment.setOnPlaceSelectedListener(new PlaceSelectionListener() {
#Override
public void onError(#NonNull Status status) {
Log.i("AutoComplete error", "error status: " + status);
}
#Override
public void onPlaceSelected(#NonNull Place place) {
LatLng placeLatLng = place.getLatLng();
MarkerOptions placeOptions = new MarkerOptions().position(placeLatLng)
.title("search")
.icon(BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE));
if(!searchMarker.isEmpty()){
Marker searchedMarker = searchMarker.get(0);
searchMarker.remove(searchedMarker);
searchedMarker.remove();
}
final Marker marker = googleMap.addMarker(placeOptions);
searchMarker.add(marker);
googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(placeLatLng, 10));
}
});
LatLng myLatLng = new LatLng(location.getLatitude(),
location.getLongitude());
// show current location
if (ActivityCompat.checkSelfPermission(requireActivity(),
Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
googleMap.setMyLocationEnabled(true);
googleMap.getUiSettings().setZoomControlsEnabled(true);
googleMap.getUiSettings().setCompassEnabled(true);
}
// show markers for all businesses on database
for(String code : mPostCodes){
try{
Address address = geocoder.getFromLocationName(code, 1).get(0);
LatLng latLng = new LatLng(address.getLatitude(), address.getLongitude());
MarkerOptions options = new MarkerOptions().position(latLng);
googleMap.addMarker(options);
}catch (IOException e){
Toast.makeText(requireActivity(), e.getMessage(), Toast.LENGTH_LONG).show();
}
}
googleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(myLatLng, 10));
}
});
}
}
});
}else {
ActivityCompat.requestPermissions(requireActivity(),
new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, 44);
}
}
private ActivityResultLauncher<String> mPermissionResult = registerForActivityResult(
new ActivityResultContracts.RequestPermission(),
result -> {
if(result){
loadMap();
}
}
);
}
Here is my fragment_maps.xml file:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".fragments.MapsFragment">
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/google_map"
android:name="com.google.android.gms.maps.SupportMapFragment"/>
<fragment
android:id="#+id/autocomplete_fragment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:name="com.google.android.libraries.places.widget.AutocompleteSupportFragment"/>
</RelativeLayout>
And here is my MainActivity.kt file:
class MainActivity : AppCompatActivity() {
private val homeFragment = HomeFragment()
private val searchFragment = SearchFragment()
private val mapFragment = MapsFragment()
private val accountFragment = AccountFragment()
private val guestAccountFragment = GuestAccountFragment()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
replaceFragment(homeFragment)
bottom_navigation.setOnItemSelectedListener {
when (it.itemId) {
R.id.id_home -> replaceFragment(homeFragment)
R.id.id_search -> replaceFragment(searchFragment)
R.id.id_map -> replaceFragment(mapFragment)
R.id.id_account -> {
if(FirebaseAuth.getInstance().currentUser == null){
replaceFragment(guestAccountFragment)
}else {
replaceFragment(accountFragment)
}
}
}
true
}
}
private fun replaceFragment(fragment : Fragment){
if(fragment != null){
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragment_container, fragment)
transaction.commit()
}
}
}
It happening beacuse your map fragment is "dying" after replacing on new fragment. Try this solution:
First of all you need add in your main activity all required fragments which can be called:
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container_view, fragment1)
.add(R.id.fragment_container_view, fragment2)
.add(R.id.fragment_container_view, fragment3)
.commit();
After adding you can change them by calling this code:
getSupportFragmentManager().beginTransaction()
.show(fragment2)
.hide(fragment1)
.commit();
Code was writen on Java but I think there is no problem

Past value from child fragment -> mainactivity cannot cast fragment_fr_event_Birthday to fragment_fr_event_wedding android studio

I have a Fragment inside the mainActivity, the fragment contains fragmentcontainerView which can be replaced by multiple child fragments with spinner onselectedListener. I want to able to pass those values from the child fragment via eg: Do something with: fragmentevent.TogetFName(); with a button in Mainactivity. In the parent fragment , I get the value from the child fragment(fragment_Birthday) with fragment_fr_event_birthday = (fragment_fr_event_Birthday) getChildFragmentManager().findFragmentById(R.id.fragment_event_child_fragment); and other value from other childfragment with frag_fr_event_wed = (fragment_fr_event_wedding) getChildFragmentManager().findFragmentById(R.id.fragment_event_child_fragment);, I know that they cannot be assigned with the different fragment class at once, but is there a clever way to do this or is there any other way I can pass value from child -> parent fragment->mainActivity
MainActivity:
public void onClick(View view){
case "Event":
Fragment_fr_Event fragment_fr_event = (Fragment_fr_Event) getSupportFragmentManager().findFragmentById(R.id.fragment_generated_mainView);
if(fragment_fr_event.TogetWedChildFcoupleName() !=null && fragment_fr_event.TogetEventType().equals("Wedding")){
testThis.setText(fragment_fr_event.TogetWedChildFcoupleName());
}if( fragment_fr_event.TogetEventType().equals("Birthday") && fragment_fr_event.TogetBirthdayFName() !=null){
testTat.setText(fragment_fr_event.TogetBirthdayFName());
}
}
ChildFragment(BirthdayFragment):
public String TogetEventBirthdayFName (){
EditText FBirthdayName = rootView.findViewById(R.id.Edittext_birthDay_FirstName);
return FBirthdayName.getText().toString();
}
ChildFragment(Wedding fragment):
public String toGetFcoupleName(){
EditText FCoupleName = rootView.findViewById(R.id.textView_wedding_Name);
return FCoupleName.getText().toString();
}
ParentFragment(EventFragment):
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Spinner TypeEventSpinner = rootview.findViewById(R.id.type_event);
TypeEventSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
String tag_items = parent.getItemAtPosition(position).toString();
switch (tag_items){
case "Wedding":
frag_fr_event_wed = new fragment_fr_event_wedding();
FragmentTransaction transaction = getChildFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_event_child_fragment, frag_fr_event_wed).disallowAddToBackStack().commit();
break;
case "Birthday":
fragment_fr_event_birthday = new fragment_fr_event_Birthday();
transaction = getChildFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_event_child_fragment , fragment_fr_event_birthday).disallowAddToBackStack().commit();
break;
}
}
#Override
public void onNothingSelected(AdapterView<?> parent) {
}
});
}
public String TogetWedChildFcoupleName(){
if(frag_fr_event_wed !=null){
frag_fr_event_wed = (fragment_fr_event_wedding) getChildFragmentManager().findFragmentById(R.id.fragment_event_child_fragment);
return frag_fr_event_wed.toGetFcoupleName();
}return "Empty";
}
public String TogetBirthdayFName(){
if(fragment_fr_event_birthday != null){
fragment_fr_event_birthday = (fragment_fr_event_Birthday) getChildFragmentManager().findFragmentById(R.id.fragment_event_child_fragment);
return fragment_fr_event_birthday.TogetEventBirthdayFName();
}
return "Empty";
}
To be honest , I couldn't understand what you did there , but i got what you want , you want to communicate with parent's parent class , the way you are doing it made it so complicated even it's not readable , BUT of course there are always a good way to do something , in your case there are Android Navigation Component , which give you the simplicity and power to do make it much more easy to handle , You can put all your fragment in one graph and from within the destinations "fragment are called destinations here" you can communicate with other fragment and the parent using actions and global actions "going from one fragment to another is called action here" parameters, but there are no need to a parent's parent here , all destinations and its parent can share one ViewModel which will allow you to share data all around your app .
You can read more if it sound good to you here

Fragment in activity startup taking more time than expected

I am a beginner-intermediate android developer. I started working with fragments in my this app. The structure of the app is:
Main activity where some important links are embedded with buttons
On clicking button from main activity, some DB tasks completed (with Room Library so all tasks are using AsyncTask) and new activity opens with link in intent extra
On getting the link, the new activity-2 adds a fragment (in itself) and also performs some DB tasks in background and then opens the link in webview of fragment.
The problem is, from 1-2 it is taking merely 0.1-0.15 seconds while on starting the task 3, it is taking 0.3-0.45 seconds so on clicking from main activity, user is getting the link opened in fragment (which has webview) in about 0.6 seconds which is making feel like app is freezing.
Here are some codes:
Activity-2:
#Override
protected void onCreate(Bundle savedInstanceState) {
prefSingleton = PrefSingleton.getInstance();
if (prefSingleton.getStorage().getBoolean(Constants.STORAGE_ENABLE_NIGHTMODE,false)){
setTheme(R.style.DarkTheme);
}
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_website_view);
Toolbar toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayShowTitleEnabled(false);
closeWebview = toolbar.findViewById(R.id.closeWebview);
downArrow = toolbar.findViewById(R.id.downArrow);
allTabs = toolbar.findViewById(R.id.allTabs);
searchIcon = toolbar.findViewById(R.id.search);
clearEditText = toolbar.findViewById(R.id.clearEditText);
selectedSEImage = toolbar.findViewById(R.id.selectedSEImage);
searchEditText = toolbar.findViewById(R.id.searchEditText);
searchEngineFrame = toolbar.findViewById(R.id.searchEngine);
searchBarLayout = toolbar.findViewById(R.id.searchBarLayout);
toolbarTitle = toolbar.findViewById(R.id.toolBarTitle);
navFrame = findViewById(R.id.navFrame);
navRecView = findViewById(R.id.navRecView);
if (prefSingleton.getStorage().getBoolean(Constants.STORAGE_ENABLE_NIGHTMODE,false)){
navFrame.setBackgroundColor(getResources().getColor(R.color.night_mode_toolbar));
}
//Activity toolbar views
closeWebview.setOnClickListener(this);
closeWebview.setOnLongClickListener(this);
downArrow.setOnClickListener(this);
allTabs.setOnClickListener(this);
searchIcon.setOnClickListener(this);
clearEditText.setOnClickListener(this);
searchEngineFrame.setOnClickListener(this);
searchEditText.setOnClickListener(this);
searchEditText.setOnKeyListener(this);
//Intent from main activity
Intent i = getIntent();
String urlType = i.getStringExtra(String.valueOf(EnumVal.SiteInfoToSend.TYPE));
final String url = i.getStringExtra(String.valueOf(EnumVal.SiteInfoToSend.URL));
String title = i.getStringExtra(String.valueOf(EnumVal.SiteInfoToSend.TITLE));
String searchedText = i.getStringExtra(String.valueOf(EnumVal.SiteInfoToSend.SEARCHED_TEXT));
fragCounter = 0;
fragTags = new ArrayList<>();
//Fragment opening
if (savedInstanceState == null){
openFragment(url, EnumVal.FragStatus.NEW, null);
}
//AdMob Ads
if (!BuildConfig.PAID_VERSION){
mInterstitialAd = new InterstitialAd(this);
mInterstitialAd.setAdUnitId(getResources().getString(R.string.interstitial_webview));
mInterstitialAd.setAdListener(new AdListener() {
#Override
public void onAdFailedToLoad(int errorCode) {
if(ConsentInformation.getInstance(WebsiteView.this).getConsentStatus() ==
ConsentStatus.NON_PERSONALIZED){
Bundle extras = new Bundle();
extras.putString("npa", "1");
mInterstitialAd.loadAd(new AdRequest.Builder()
.addNetworkExtrasBundle(AdMobAdapter.class,extras).build());
} else {
mInterstitialAd.loadAd(new AdRequest.Builder().build());
}
}
});
if(ConsentInformation.getInstance(WebsiteView.this).getConsentStatus() == ConsentStatus.NON_PERSONALIZED){
Bundle extras = new Bundle();
extras.putString("npa", "1");
mInterstitialAd.loadAd(new AdRequest.Builder()
.addNetworkExtrasBundle(AdMobAdapter.class,extras).build());
} else {
mInterstitialAd.loadAd(new AdRequest.Builder().build());
}
}
//Toolbar search edit text will be enabled in below case
if (urlType.equals(EnumVal.Type.SEARCHED_TEXT.toString())){
searchBarLayout.setVisibility(View.VISIBLE);
searchIcon.setImageResource(R.drawable.close_icon);
searchEditText.setText(searchedText);
searchEditText.clearFocus();
searchEditText.setCursorVisible(false);
this.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
if (searchEditText.hasFocus())
searchEditText.setCursorVisible(true);
}
searchEditText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
clearEditText.setVisibility(View.VISIBLE);
}
});
//Navigation view in toolbar
navRecView.setLayoutManager(new LinearLayoutManager(this));
mViewModel = ViewModelProviders.of(this).get(DbViewModel.class);
mViewModel.getSitesWithClicksByIsShown().observe(this, new Observer<List<SitesWithClicks>>() {
#Override
public void onChanged(#Nullable List<SitesWithClicks> sitesWithClicks) {
if (sitesWithClicks!= null && sitesWithClicks.size()>0){
int reArrSite = prefSingleton.getStorage().getInt(Constants.STORAGE_REARRANGESITE_NAV,
Constants.SORTING_PRIORITY);
sitesWithClicks = Utility.sortSitesData(reArrSite, sitesWithClicks);
navAdapter = new ListRecViewAdapter(EnumVal.DialogType.NAVBAR_ITEMS,
sitesWithClicks, WebsiteView.this);
navRecView.setAdapter(navAdapter);
}
}
});
}
Open-fragment method:
public void openFragment(String url, EnumVal.FragStatus fragStatus, String toOpenTag){
if(navFrame.getVisibility() == View.VISIBLE)
downArrow.performClick();
if(fragStatus == EnumVal.FragStatus.NEW){
String fragTag = getNextFragTag();
WebsiteViewFragment fragment = WebsiteViewFragment.newInstance(url, fragTag);
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
if(fragCounter == 1){
fragmentTransaction.replace(R.id.fragment, fragment, fragTag);
currentFrag = fragTag;
} else {
if(getSupportFragmentManager().findFragmentByTag(currentFrag) != null){
fragmentTransaction.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out);
fragmentTransaction.hide(getSupportFragmentManager().findFragmentByTag(currentFrag));
}
fragmentTransaction.add(R.id.fragment, fragment, fragTag);
currentFrag = fragTag;
}
fragmentTransaction.commit();
fragTags.add(new TabDetail(fragTag,"Tab - 1",null));
} else if (fragStatus == EnumVal.FragStatus.OLD){
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
if(getSupportFragmentManager().findFragmentByTag(currentFrag) != null){
fragmentTransaction.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out);
fragmentTransaction.hide(getSupportFragmentManager().findFragmentByTag(currentFrag));
}
fragmentTransaction.show(getSupportFragmentManager().findFragmentByTag(toOpenTag));
fragmentTransaction.commit();
currentFrag = toOpenTag;
}
}
And Fragment has just Webview in it where webview is loading lot of websettings. But thing is reaching till fragment is taking time ,like user clicks from main activity and after about 0.6 seconds, fragment is opening while activity which has fragment is taking just 0.1 second to open so this time is (maybe) related to attaching fragment or something?
Can anyone please explain me, where am I making mistake?
I had some doubt regarding this:
Is the problem webview (in fragment), which has a bunch of websettings ?
Is attaching fragment to activity-2 taking more time?
Is UI part of activity-2 taking time (like toolbar setup which has 4 buttons) and after that fragment is attaching to it, which is resulting in more time consumption ?
Or it is a normal situation ?
Can someone, please, explain me the way to make it to <0.2 seconds for whole tasks? Thanks in advance.

Intent from fragment to mainActivity

I'm trying to send intent from fragment to MainActivity. But while doing startActivity(intent) at this point app is working good but every time I click on listview fragment is refreshing and listview starts from 0 index again. What is the best way to send the intent from the fragment?
I have two fragments and one main activity. So the user can see both the fragments at the same time. fist fragment is a list of cities and the second fragment is the description of the city.
Thank you very much for your time and assistance in this matter.
Please check my code below:
public class MainActivity extends AppCompatActivity {
private String cityName;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
fragmentManage();
}
public void fragmentManage() {
CityInformationFragment cityInformationFragment = new
CityInformationFragment();
CityList cityList = new CityList();
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.fragment2holder, cityList);
fragmentTransaction.add(R.id.fragment1holder, cityInformationFragment);
fragmentTransaction.commit();
Intent intent = getIntent();
cityName = intent.getStringExtra(CityList.PUT_EXTRA_KEY);
Bundle bundle = new Bundle();
bundle.putString(CityList.PUT_EXTRA_KEY, cityName);
cityInformationFragment.setArguments(bundle);
}
}
My CityList fragment:
public class CityList extends Fragment implements
AdapterView.OnItemClickListener {
String[] cityList;
ListView listView;
protected static final String PUT_EXTRA_KEY = "city";
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup
container, Bundle savedInstanceState) {
// arrayAdapter.addAll(cityList);
cityList = getResources().getStringArray(R.array.cities);
ArrayAdapter<CharSequence> arrayAdapter = new ArrayAdapter <CharSequence>(getActivity(), android.R.layout.simple_list_item_1, cityList);
View view = inflater.inflate(R.layout.citylistframent, container, false);
listView = (ListView) view.findViewById(R.id.cityList);
listView.setAdapter(arrayAdapter);
listView.setOnItemClickListener(this);
return view;
}
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
TextView textView = (TextView) view;
String city = textView.getText().toString();
Intent intent = new Intent(getActivity(), MainActivity.class);
intent.putExtra(PUT_EXTRA_KEY, city);
startActivity(intent);
}
}
My CityInformationFragment:
public class CityInformationFragment extends Fragment {
String vancouver = "Vancouver, a bustling west coast seaport in British Columbia, is among Canada’s densest, most ethnically diverse cities." +
" A popular filming location, it’s surrounded by mountains, and also has thriving art, theatre and music scenes." +
" Vancouver Art Gallery is known for its works by regional artists, while the Museum of Anthropology houses preeminent First Nations collections.";
String calgary = "Calgary, a cosmopolitan Alberta city with numerous skyscrapers, owes its rapid growth to its status as the centre of Canada’s oil industry. However," +
" it’s still steeped in the western culture that earned it the nickname “Cowtown,”" +
" evident in the Calgary Stampede, its massive July rodeo and festival that grew out of the farming exhibitions once presented here.";
String kamloops = "Kamloops is a Canadian city in British Columbia, where the North and South Thompson rivers meet." +
" Sun Peaks Resort’s hiking trails, bike park and numerous ski runs lie to the northeast. Cougars and bears inhabit the British Columbia Wildlife Park east of town." +
" West, above Kamloops Lake are clay hoodoos (or spires). The riverside Secwepemc Museum & Heritage Park features the remains of a 2,000-year-old village.";
String toronto = "Toronto, the capital of the province of Ontario, is a major Canadian city along Lake Ontario’s northwestern shore." +
" It's a dynamic metropolis with a core of soaring skyscrapers, all dwarfed by the iconic, free-standing CN Tower. " +
"Toronto also has many green spaces, from the orderly oval of Queen’s Park to 400-acre High Park and its trails, sports facilities and zoo.";
String saskatoon = "Saskatoon is a city straddling the South Saskatchewan River in Saskatchewan, Canada. " +
"North along the riverside Meewasin Trail is Wanuskewin Heritage Park, with exhibitions exploring indigenous culture. " +
"On the trail’s southern stretch, native wildlife inhabit the prairie grasslands of Beaver Creek Conservation Area. " +
"East of the river, the Saskatoon Forestry Farm Park & Zoo has manicured gardens and a children’s zoo.";
TextView textView;
String cityName = "";
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.cityinformation, container, false);
textView = view.findViewById(R.id.cityInformation);
cityName = getArguments().getString(CityList.PUT_EXTRA_KEY);
// Toast.makeText(getContext(), cityName, Toast.LENGTH_SHORT).show();
setInformation();
return view;
}
public String getVancouver() {
return vancouver;
}
public String getCalgary() {
return calgary;
}
public String getKamloops() {
return kamloops;
}
public String getToronto() {
return toronto;
}
public String getSaskatoon() {
return saskatoon;
}
public void setInformation() {
if (cityName != null) {
if (cityName.equals("")) {
textView.setText(getKamloops());
} else if (cityName.equals("Calgary")) {
textView.setText(getCalgary());
} else if (cityName.equals("Kamloops")) {
textView.setText(getKamloops());
} else if(cityName.equals("Calgary")) {
textView.setText(getCalgary());
} else if(cityName.equals("Saskatoon")){
textView.setText(getSaskatoon());
} else if(cityName.equals("Toronto")) {
textView.setText(getToronto());
} else if(cityName.equals("Vancouver")){
textView.setText(getVancouver());
}
} else {
textView.setText(getKamloops());
}
}
}
Use eventbus it will solve your problem and you dont need to call activity, fragments are working inside activity read documentation you understand me
Intents are only usable for sending data on an Activity level. To pass data between fragments we need to create our own interfaces.
As describe here Communicating with Other Fragments
You need to create a listener/callback in your Activity and Fragment. Whenever there is a change that you want to pass to Activity, send it via the listener. Read more at Communicating with Other Fragments.
First, define the listener with interface in the Fragment:
public class CityList extends Fragment implements AdapterView.OnItemClickListener {
private CityListListener mListener;
// Host Activity must implement this listener.
public interface CityListListener {
public void onItemClicked(String city);
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
// Makes sure that the host activity has implemented the listener
try {
mListener = (CityListListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString()
+ " must implement CityListListener");
}
}
...
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
TextView textView = (TextView) view;
String city = textView.getText().toString();
// tell the host Activity about the item click.
mListener.onItemClicked(city);
}
}
Now, you need to implement the listener in your Activity:
public class MainActivity extends AppCompatActivity
implements CityList.CityListListener {
...
#Override
public void onItemClicked(String city) {
// This will listen to every item click in CityList Fragment
// Do something about the city.
}
}

Android ListView not refreshing after dataset changed, though I have used the Adapter.notifyDataSetChanged()

I know similar questions had been asked here a couple of times, but none of them could help me with my problem, so I will just have to ask again.
What I have is an app that has a fragment that holds a ListView in the main activity and I used a PullableListView so that when I drag the ListView up, it will trigger the onLoadMore() callback method to load more data from the server. Once data loaded, the data will be saved to a SQLiteDB and then used by the ListView to show the updated data.
The is my PullableListViewFragment.java:
public class PullableListViewFragment extends Fragment implements
OnItemClickListener {
private ListView listView;
private PullToRefreshLayout ptrl;
private MissionDB mMissionDB;
private List<MissionEntity> mData;
private MissionListAdapter mAdapter;
/**
* Specify the exact mission that will be displayed in MissionDetailActivity
*/
private int mIndexOfMission;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View pullableLayout = inflater.inflate(R.layout.pullable_layout, container, false);
listView = (ListView) pullableLayout.findViewById(R.id.content_view);
listView.setDivider(null);
ptrl = (PullToRefreshLayout) pullableLayout.findViewById(R.id.refresh_view);
ptrl.setOnRefreshListener(new RefreshListener());
loadData();
Log.d(Constants.LOG_TAG, "onCreateView Called from PullableListViewFragment");
return pullableLayout;
}
/**
* Initialise ListView
*/
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mAdapter = new MissionListAdapter(getActivity(), mData);
listView.setAdapter(mAdapter);
listView.setOnItemClickListener(this);
mAdapter.notifyDataSetChanged();
Log.d(Constants.LOG_TAG, "onActivityCreated Called from PullableListViewFragment");
}
/**
* Load data from db
*/
private void loadData() {
mData = new ArrayList<>();
mMissionDB = MissionDB.getInstance(MyApplication.getContext());
mData = mMissionDB.loadMission();
}
/**
* OnItemClick event
*/
#Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
Intent intent = new Intent(getActivity(), MissionDetailActivity.class);
mIndexOfMission = mData.get((int) id).getId();
intent.putExtra("Position", mIndexOfMission);
startActivity(intent);
}
}
And this is the RefreshListener.java:
public class RefreshListener implements PullToRefreshLayout.OnRefreshListener {
private MissionDB mMissionDB = MissionDB.getInstance(MyApplication.getContext());
#Override
public void onLoadMore(final PullToRefreshLayout pullToRefreshLayout) {
// LoadMore
new Handler() {
#Override
public void handleMessage(Message msg) {
/** When drag up, load more mission that is older and not out of date */
mMissionDB.open();
int id = mMissionDB.getMaxOrMinId("MIN");
final JSONObject oldMission = new JSONObject();
try {
oldMission.put("platform", "1");
oldMission.put("more", 0);
oldMission.put("id", id);
oldMission.put("size", 1);
} catch (JSONException e) {
e.printStackTrace();
}
Thread t = new Thread(new Runnable() {
#Override
public void run() {
HttpRequest.sendHttpRequest(Constants.PULLORDRAG_TO_LOAD_MISSION_URL, oldMission, new HttpCallbackListener() {
#Override
public void onFinish(String response) {
MissionEntity mMission = new MissionEntity();
/** Save new mission to mission database */
try {
JSONObject jsonObject = new JSONObject(response);
JSONArray missionList = jsonObject.getJSONArray("taskList");
for (int i = 0; i < missionList.length(); i++) {
JSONObject mission = missionList.getJSONObject(i);
mMission.setId(mission.getInt("id"));
mMission.setDownloadUrl(mission.getString("appPath"));
mMission.setCreateTime(mission.getString("taskId"));
mMission.setImageUrl(mission.getString("appImg"));
mMission.setTitleName(mission.getString("appName"));
mMission.setRemainTime("Remain: 1d 11h 23m 36s");
mMission.setParticipant("135");
mMission.setCreator("Google");
mMission.setRequirement(mission.getString("description"));
mMission.setRewards("TODO");
mMission.setAttention("TODO");
mMission.setValidDate(mission.getString("deadline"));
mMission.setAccepted("0");
mMission.setCollected("0");
mMission.setAccomplished("0");
mMissionDB.saveMission(mMission);
}
} catch (JSONException e) {
e.printStackTrace();
}
}
#Override
public void onError(Exception e) {
e.printStackTrace();
Log.e(Constants.LOG_TAG, "Error while loading more");
}
});
}
});
try {
t.start();
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
pullToRefreshLayout.loadmoreFinish(PullToRefreshLayout.SUCCEED);
//==================Update 1==================
ListView list = (ListView) pullToRefreshLayout.findViewById(R.id.content_view);
List<MissionEntity> missionList = mMissionDB.loadMission();
Log.d(Constants.LOG_TAG, "mission size" + missionList.size());
MissionListAdapter adapter = (MissionListAdapter) list.getAdapter();
adapter.setData(missionList);
adapter.notifyDataSetChanged();
list.setAdapter(adapter);
//==================Update 1==================
}
}.sendEmptyMessageDelayed(0, 1000);
}
}
The PullToRefreshLayout is a custom RelativeLayout that defined a inner interface OnRefreshListener that will be called once the onLoadMore() callback method is called.
The ListView I use in the Fragment is a PullableListView that implemented a Pullable interface that can drag up.
My Pullable.java:
public interface Pullable
{
/**
* If pull up is not needed, set canPullUp to false
*
* #return true if the View can pull up
*/
boolean canPullUp();
}
This is the fragment's layout.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#f2f2f2"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="5dp" />
<pulltorefresh.PullToRefreshLayout
android:id="#+id/refresh_view"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="#layout/refresh_head"/>
<!-- Supports all view that implemented the Pullable interface -->
<pulltorefresh.PullableListView
android:id="#+id/content_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<include layout="#layout/load_more"/>
</pulltorefresh.PullToRefreshLayout>
</LinearLayout>
The problem is when the data in the SQLiteDB is changed after the onLoadMore() method, the ListView in the fragment doesn't refresh itself unless I navigate to another fragment within the same main activity and then navigate back.
I tired all the ways I can find here and none of them help.
Could anyone tell me how can I make the ListView refresh itself when the data in the SQLiteDB changed.
[Update 1] I've added some code in the onLoadMore() callback, the size of the data remains the same after I get some data from the server, i think this is the problem why the ListVie is not refreshing, what's interesting is if I put a Thread.sleep(500) before mMission.loadMission() the size of the data is correct and everything is fine. Any idea why?
In onLoadMore method, you just only load data and save into database. After load data, you should update the data source of the adapter and call adapter.notifyDataSetChanged() to refresh the listview. or you can notify the fragment to reload data from database.
Make sure it get data when load more first. And I think your mMissionDB is changed indeed, but what about mData? Your adapter is use mData as datasource actually, so you should update mData, and call adapter.notifyDataSetChanged() to refresh the listview. Hope it can help you.

Categories

Resources