Situation:
I am making a chat app with Skype like UI. The contacts recycler view is on the left side.
I have the custom ActionBar based theme.
I need to set the title in the ActionBar onClick.
So, basically, the onClick method is in the Adapter. OnClick of the contacts, the method is passed to the Activity with ActionBar and the name of the contact should come in the title.
The getActionBar() runs perfectly and the Title is set in onCreate method. But, app crashes when I do the same in method outside onCreate.
I referred links here and here but I couldn't solve my issue.
Please guide me regarding the same.
Example:
ChatActivity extends Activity {
//..onCreate here
if(getActionBar() != null) {
String title = " Chat: ";
if(userName != null) {
title = title + userName;
}
getActionBar().setTitle(title);
}
// onCreate finishes
// onContactChange
public void onContactChange(int position, ContactsVO addContact) {
userName = addContact.getName().toString();
String url = addContact.getDP();
if(getActionBar() != null) { //App crashes here
String title =" Chat: ";
if(userName != null)
title = title + userTo;
getActionBar().setTitle(title);
}
}
}
Async Task is called, webservice returns the data which is set in the Adapter.
Now,
in Adapter,
ChatActivity c1 = new ChatActivity();
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
ContactsVO contactsvo = data.get(position);
holder.tv.setText(contactsvo.getName());
String url = contactsvo.getDP();
Glide.with(getContext())
.load(url)
.crossFade()
.into(holder.img);
holder.row.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
c1.onContactChange(position,contactsvo); //method called here.
}
});
}
Stack Trace
E/ACRA: ACRA caught a NullPointerException exception for com.chat
Building report. 11-20 15:51:23.278 12797-12941/? E/ACRA: com.chat
fatal error : Attempt to invoke virtual method 'android.view.View
android.view.Window.getDecorView()' on a null object reference
java.lang.NullPointerException: Attempt to invoke virtual method
'android.view.View android.view.Window.getDecorView()' on a null
object reference
at android.app.Activity.initWindowDecorActionBar(Activity.java:2397)
at android.app.Activity.getActionBar(Activity.java:2339)
at com.chat.activities.ChatActivity.onContactChange(ChatActivity.java:276)
at com.chat.utilities.adapters.ChatCustomAdapter$1.onClick(ChatCustomAdapter.java:74)
at android.view.View.performClick(View.java:5678)
at android.view.View$PerformClick.run(View.java:22667)
at android.os.Handler.handleCallback(Handler.java:836)
at android.os.Handler.dispatchMessage(Handler.java:103)
at android.os.Looper.loop(Looper.java:203)
at android.app.ActivityThread.main(ActivityThread.java:6293)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1065)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:926)
Please guide me to solve the solution for the same.
My suggestion is to use setSupportActionBar() for whole activity.
Here in your layout.
<android.support.v7.widget.Toolbar
android:id="#+id/home_activity_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_scrollFlags="scroll|enterAlways" />
//OnCreate
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home_page);
toolbar = (Toolbar) findViewById(R.id.home_activity_toolbar);
configureHomeToolBar();
}
private void configureHomeToolBar() {
toolbar.setBackgroundColor(ContextCompat.getColor(getApplicationContext(), R.color.colorPrimary));
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayShowHomeEnabled(true);
getSupportActionBar().setDisplayShowTitleEnabled(false);
getSupportActionBar().setIcon(R.drawable.my_logo);
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
}
Then you will be able to use it by calling getSupportActionBar() anywhere in your activity such as:
getSupportActionBar().setDisplayUseLogoEnabled(false);
getSupportActionBar().setTitle(titlesArray[someIndex]);
Related
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();
}
}
// ...
});
}
}
I have a problem with calling activity from fragment class. More:
XML of adapter's layout:
<TextView
style="#style/LiHeadLogin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:foreground="?android:attr/selectableItemBackground"
android:clickable="true"
android:onClick="openProfile"
android:id="#+id/genFrom" />
Code from main activity that calls fragment:
public void openProfile(View v) {
Fragment_Questions frau = new Fragment_Questions();
frau.openProfile(v);
}
Code from fragment class:
public void openProfile(View v) {
View row = (View) v.getParent();
TextView child2 = (TextView) row.findViewById(R.id.genFromlogin);
String child3 = child2.getText().toString();
Intent ini = getActivity().getIntent();
String c_username = ini.getStringExtra(MainActivity.KEY_USERNAME);
String c_password = ini.getStringExtra(MainActivity.KEY_PASSWORD);
Intent ini2 = new Intent(context, User.class);
ini2.putExtra(MainActivity.KEY_USERNAME, c_username);
ini2.putExtra(MainActivity.KEY_PASSWORD, c_password);
ini2.putExtra(MainActivity.KEY_USER, child3);
getActivity().startActivity(ini2);
}
Errors:
java.lang.IllegalStateException: Could not execute method for android:onClick
...
...
...
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.Intent android.support.v4.app.FragmentActivity.getIntent()' on a null object reference
P.S.
1. App opens default activity
2. User calls a new activity with passing intent data
3. New activity calls fragment
The first (main) activity:
Intent intent_settings = getIntent();
String c_username = intent_settings.getStringExtra(MainActivity.KEY_USERNAME);
String c_password = intent_settings.getStringExtra(MainActivity.KEY_PASSWORD);
Intent intent_settings_1 = new Intent(this, NewFeed.class);
intent_settings_1.putExtra(MainActivity.KEY_USERNAME, c_username);
intent_settings_1.putExtra(MainActivity.KEY_PASSWORD, c_password);
intent_settings_1.setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivity(intent_settings_1);
Okay, and you should know that all fragments are called by viewpager with tab layout.
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
Fragment_Home tab1 = new Fragment_Home();
return tab1;
case 1:
Fragment_Questions tab2 = new Fragment_Questions();
return tab2;
default:
return null;
}
}
You can only access the activity with getActivity() between calls to onAttach() and onDetach().
Fragment_Questions itself only seems to serve the purpose to start a new activity, so you could just move all the logic to the click listener of the activity and don't use the fragment at all.
If there's more about the fragment and you're actually planning to add it to the activity sometime in the future it still seems better to have this logic separated from the fragment. You only use getActivity() and magically context in there. You could just make it static and give the activity as parameter instead.
public static void openProfile(Activity activity, View v) {
View row = (View) v.getParent();
TextView child2 = (TextView) row.findViewById(R.id.genFromlogin);
String child3 = child2.getText().toString();
Intent ini = activity.getIntent();
String c_username = ini.getStringExtra(MainActivity.KEY_USERNAME);
String c_password = ini.getStringExtra(MainActivity.KEY_PASSWORD);
Intent ini2 = new Intent(activity, User.class);
ini2.putExtra(MainActivity.KEY_USERNAME, c_username);
ini2.putExtra(MainActivity.KEY_PASSWORD, c_password);
ini2.putExtra(MainActivity.KEY_USER, child3);
activity.startActivity(ini2);
}
You should add Fragment_Questions to Activity.
For example,In Activity layout:
<LinearLayout 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"
tools:context=".MainActivity" >
<fragment
android:id="#+id/fragment_question"
android:name="com.example.view.Fragment_Questions"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
In Acitivity.java:
questionFragment = (Fragment_Questions) findViewById(fragment_question);
and then, you can user OnClick Event like this:
questionFragment.openProfile(v);
You are getting Null Pointer Exception because your fragment is not attached to Activity. You are just instantiating the fragment and then calling its openProfile(View v) method which is a wrong way to handle events. If you already attached fragment then get the instance of framgent using
questionFragment = (Fragment_Questions) findViewById(fragment_question);
You can read below article on how to communicate from Fragments to activity
https://developer.android.com/training/basics/fragments/communicating.html
As I have understanded the steps of the user interferance you have to replace getIntent() in the fragment with the intent that starts it which is intent_settings i think
Also make the that intent static in order to share it
And make if statment in order not to have null exception
if(theIntent.hasPutextra)
{getExtra.....}
Do you extends Fragment OR FragmetnActivity?
if you extend FragmetnActivity (as i think) then dont use getActivity()
Also add the code for the intent that started your current activity
Fragment_Questions frau = new Fragment_Questions();
frau.openProfile(v);
You just created new fragment you didnt attached it to the activity?
use FragmentManager.
if you trying to use fragment inside ViewPager use THIS guide
sorry for the stupid and repetitive question. I am new to android programming and in progress of making an app which can scan QR Codes and save the data to an SQLite database and show it in a ListView of another activity.
Also, the ListView has a "search" function which enables the user to type text which will bring up a similar result (if found in the listview) from the items in the listview. Lastly, the delete function deletes the item from the ListView when the user taps on the item.
The application worked fine and opened the ListView Activity showing the ListView with the contents until I integrated the search and delete function.
The application states that I am trying to invoke a virtual method on a null object reference. I have looked up on how to solve it, but couldn't find anything.
Now it shows this error:
07-31 18:52:31.688 2900-2900/app.num.barcodescannerproject E/AndroidRuntime: FATAL EXCEPTION: main
Process: app.num.barcodescannerproject, PID: 2900
java.lang.RuntimeException: Unable to start activity ComponentInfo{app.num.barcodescannerproject/app.num.barcodescannerproject.ListActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void app.num.barcodescannerproject.CustomCursorAdapter.changeCursor(android.database.Cursor)' on a null object reference
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2379)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2442)
at android.app.ActivityThread.access$800(ActivityThread.java:156)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1351)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:211)
at android.app.ActivityThread.main(ActivityThread.java:5389)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1020)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:815)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void app.num.barcodescannerproject.CustomCursorAdapter.changeCursor(android.database.Cursor)' on a null object reference
at app.num.barcodescannerproject.ListActivity.onCreate(ListActivity.java:41)
at android.app.Activity.performCreate(Activity.java:5990)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1106)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2332)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2442)
at android.app.ActivityThread.access$800(ActivityThread.java:156)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1351)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:211)
at android.app.ActivityThread.main(ActivityThread.java:5389)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1020)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:815)
07-31 18:52:32.968 2900-2900/app.num.barcodescannerproject I/Process: Sending signal. PID: 2900 SIG: 9
Here is the code for the Activity which sends the Intent with the data to the ListView Activity:
public void saveOnClick (View saveButton){
String prescriptionName = scanResult.getText().toString();
if (prescriptionName.length() != 0) {
Intent newIntent = new Intent (this, ListActivity.class);
newIntent.putExtra("tag_person_name", prescriptionName);
startActivity(newIntent);
finish();
}
}
And this is the code for the ListActivity with the ListView, search and delete functions:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
databaseHelper = new PersonDatabaseHelper(this);
listView = (ListView) findViewById(R.id.list_data);
databaseHelper.insertData(getIntent().getExtras().getString("tag_person_name"));
customAdapter.changeCursor(databaseHelper.getAllData());
listView.setOnItemClickListener(listContentOnItemClickListener);
customAdapter = new CustomCursorAdapter(ListActivity.this, databaseHelper.getAllData());
listView.setAdapter(customAdapter);
Cursor cursor = databaseHelper.getAllData();
EditText myFilter = (EditText) findViewById(R.id.editText);
myFilter.addTextChangedListener(new TextWatcher() {
public void afterTextChanged(Editable s) {
}
public void beforeTextChanged(CharSequence s, int start,
int count, int after) {
}
public void onTextChanged(CharSequence s, int start,
int before, int count) {
customAdapter.getFilter().filter(s.toString());
}
});
customAdapter.setFilterQueryProvider(new FilterQueryProvider() {
public Cursor runQuery(CharSequence constraint) {
return databaseHelper.fetchDataByName(constraint.toString());
}
});
}
//public void onClickEnterData(View btnAdd) {
// startActivityForResult(new Intent(this, ResultActivity.class), ENTER_DATA_REQUEST_CODE);
//}
// #Override
//protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// super.onActivityResult(requestCode, resultCode, data);
// if (requestCode == ENTER_DATA_REQUEST_CODE && resultCode == RESULT_OK) {
// databaseHelper.insertData(data.getExtras().getString("tag_person_name"));
// customAdapter.changeCursor(databaseHelper.getAllData());
// }
// }
private OnItemClickListener listContentOnItemClickListener
= new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
// TODO Auto-generated method stub
Cursor cursor = (Cursor) parent.getItemAtPosition(position);
final int item_id = cursor.getInt(cursor.getColumnIndex(PersonDatabaseHelper.PERSON_TABLE_COLUMN_ID));
String item_name = cursor.getString(cursor.getColumnIndex(PersonDatabaseHelper.PERSON_TABLE_COLUMN_NAME));
AlertDialog.Builder myDialog
= new AlertDialog.Builder(ListActivity.this);
myDialog.setTitle("Delete?");
TextView dialogTxt_id = new TextView(ListActivity.this);
ViewGroup.LayoutParams dialogTxt_idLayoutParams
= new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
dialogTxt_id.setLayoutParams(dialogTxt_idLayoutParams);
dialogTxt_id.setText(String.valueOf(item_id));
TextView dialogC1_id = new TextView(ListActivity.this);
ViewGroup.LayoutParams dialogC1_idLayoutParams
= new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
dialogC1_id.setLayoutParams(dialogC1_idLayoutParams);
dialogC1_id.setText(item_name);
LinearLayout layout = new LinearLayout(ListActivity.this);
layout.setOrientation(LinearLayout.VERTICAL);
layout.addView(dialogTxt_id);
layout.addView(dialogC1_id);
myDialog.setView(layout);
myDialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {
// do something when the button is clicked
public void onClick(DialogInterface arg0, int arg1) {
databaseHelper.delete_byID(item_id);
updateList();
}
});
myDialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
// do something when the button is clicked
public void onClick(DialogInterface arg0, int arg1) {
}
});
myDialog.show();
}
};
#Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
databaseHelper.close();
}
private void updateList() {
customAdapter.changeCursor(databaseHelper.getAllData());
}
}
According to what I see, the error is here, at the changeCursor:
databaseHelper.insertData(getIntent().getExtras().getString("tag_person_name"));
customAdapter.changeCursor(databaseHelper.getAllData());
listView.setOnItemClickListener(listContentOnItemClickListener);
I have also noticed that when I remove the changeCursor, the error will be shown at the setOnItemClickListener, stating that this virtual method is also being invoked on a null object reference.
Is there anything I can do about this? I have searched far and wide across many sites but cannot find the solution anywhere. Perhaps I'm blind.
UPDATE: Thank you for the help, I have realised that I did not initialise the customAdapter first. However, now that I did, I keep getting two exactly same NPE errors as the one with the customAdapater, except the first one is at the setOnItemClickListener and the other at the listView.setAdapter. Both are apparently also invoked on a null object. However this time, unlike with the customAdapter, I really can't spot the mistake. Any help?
You are initializing the CursorAdapter after its first use. This is wrong as you first need to create the CursorAdapter object before using it.
...
databaseHelper.insertData(getIntent().getExtras().getString("tag_person_name"));
/* This line is moved from below to this place i.e before you use the changeCursor method on it*/
customAdapter = new CustomCursorAdapter(ListActivity.this, databaseHelper.getAllData());
customAdapter.changeCursor(databaseHelper.getAllData());
listView.setOnItemClickListener(listContentOnItemClickListener);
...
You are getting a Null Pointer Exception (NPE) because you are using an object that has not been created yet using the new operator
Also should this line go first before customAdpater is declared?
Cursor cursor = databaseHelper.getAllData();
Ok so I know I should use interfaces to retrieve information from AsyncTask because they run in separate threads but I have been trying in vain to get this right. Can someone guide me to what is wrong with my code?
First I thought I was getting null pointer exception at callback.onJsonReady(movie) because I was still trying to reach movie that was not processed despite my attempt. So I tried sleeping it for 5000ms before it retrieves but I still get a null pointer exception. please help me.
If you need to look at more of my code, please tell me also.
Below is the method where I set up my interface
protected void onPostExecute(String s) {
super.onPostExecute(s);
processData(getmData());
}
private void processData (String mData){
try {
final String MOVIE_TITLE = "Title";
JSONObject jsonObject = new JSONObject(mData);
Log.v(LOG_TAG, mData);
String title = jsonObject.getString(MOVIE_TITLE);
Movie movie = new Movie(title);
callback.onJsonReady(movie);
Log.v(LOG_TAG, "Title of the movie is " + movie.getTitle());
}catch (JSONException e){
Log.e(LOG_TAG, "Error retrieving JsonData");
e.printStackTrace();
}
}
}
Below is my class where I call the interface
public class ResultsPage extends AppCompatActivity implements ParseJsonData.ParseJsonCallback{
private final String LOG_TAG = getClass().getSimpleName();
private TextView title;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_results_page);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
setTextViews();
}
private void setTextViews () {
Bundle bundle = getIntent().getExtras();
String movieTitle = bundle.getString("title");
Log.v(LOG_TAG, "title recieved is : " + movieTitle);
ParseJsonData parseJsonData = new ParseJsonData(movieTitle, this);
parseJsonData.execute();
}
#Override
public void onJsonReady(Movie movie) {
title.setText(movie.getTitle());
}
}
My logcat is
03-14 18:03:02.931 30827-30827/? E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.jc.tagyourmovie, PID: 30827
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference
at com.jc.tagyourmovie.ResultsPage.onJsonReady(ResultsPage.java:44)
at com.jc.tagyourmovie.ParseJsonData$ParseJsonDataBackground.processData(ParseJsonData.java:83)
at com.jc.tagyourmovie.ParseJsonData$ParseJsonDataBackground.onPostExecute(ParseJsonData.java:69)
at com.jc.tagyourmovie.ParseJsonData$ParseJsonDataBackground.onPostExecute(ParseJsonData.java:53)
at android.os.AsyncTask.finish(AsyncTask.java:651)
at android.os.AsyncTask.access$500(AsyncTask.java:180)
at android.os.AsyncTask$InternalHandler.handleMessage(AsyncTask.java:668)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:158)
at android.app.ActivityThread.main(ActivityThread.java:7224)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
Your Textview title is not initialized... that is the reason
you need to do something like:
title = (TextView)findViewById(R.id.<your_tv_id>);
Error is in title.setText line. Logcat says so >
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.TextView.setText(java.lang.CharSequence)' on a null object reference
Seems you haven't initialize your textview named title, initialize it before using it.
Your code declared a textview named title
private TextView title;
you have to initialize this textview in onCreate() like below
title = (TextView)findViewById(R.id.TEXTVIEWID);
You need to move code that changes UI to the onPostExecute part of the AsyncTask.
callback.onJsonReady(movie);
This part can be in the doInBackground, but use a field of the AsynchTask rather than a local var so you can access it in the onPostExecute:
Movie movie = new Movie(title);
private TextView title;
You need to give a value to title, something like this:
title = (TextView) R.findViewById(R.id.textviewid);
And after that you can call setText() on it.
Replace textviewid with your textview's id in xml file.
In my app I have a custom AlertDialog (handled by the system using showDialog()) which contains a tabhost with 2 tabs. In one of the tabs is a spinner. I can rotate my screen without problems as long as the spinner isn't open (spinner dialog displayed). If the spinner is open during rotation, I get this:
FATAL EXCEPTION: main
java.lang.IllegalArgumentException: View not attached to window manager
at android.view.WindowManagerImpl.findViewLocked(WindowManagerImpl.java:355)
at android.view.WindowManagerImpl.removeView(WindowManagerImpl.java:200)
at android.view.Window$LocalWindowManager.removeView(Window.java:432)
at android.app.Dialog.dismissDialog(Dialog.java:278)
at android.app.Dialog.access$000(Dialog.java:71)
at android.app.Dialog$1.run(Dialog.java:111)
at android.app.Dialog.dismiss(Dialog.java:268)
at android.widget.Spinner.onDetachedFromWindow(Spinner.java:89)
at android.view.View.dispatchDetachedFromWindow(View.java:6173)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1164)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1162)
at android.view.ViewRoot.dispatchDetachedFromWindow(ViewRoot.java:1746)
at android.view.ViewRoot.doDie(ViewRoot.java:2757)
at android.view.ViewRoot.handleMessage(ViewRoot.java:1995)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:130)
at android.app.ActivityThread.main(ActivityThread.java:3683)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:507)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
at dalvik.system.NativeStart.main(Native Method)
So...
1 - Is it possible to programmatically close the spinner during onPause()?
2 - Is there a different method I should be using?
3 - If there isn't a proper solution, how do I catch this particular Exception? (Bad practice I know, but the crashing needs to be stopped, and since the activity reconstructs itself properly after being destroyed, it wouldn't matter anyway.
... And please for the love of all that is holy, do not suggest that I add android:configChanges="orientation" to my activity declaration. It amazes me how often that is accepted as the official fix-all for orientation issues.
Edit for additional info
Here is my dialog creation code for reference:
static final int ID_DIALOG_CHOOSER = 1;
int pref_location;
private Dialog dialog;
...
protected Dialog onCreateDialog(int id)
{
switch(id)
{
case ID_DIALOG_CHOOSER:
dialog = show(ID_DIALOG_CHOOSER);
break;
}
return dialog;
}
...
showDialog(DialogView.ID_DIALOG_CHOOSER);
...
Dialog show(final int dialogType)
{
if (dialogType == ID_DIALOG_CHOOSER)
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
// inflate tabhost layout
View tabHostLayout = (View) inflater.inflate(R.layout.tabhost_layout, null);
FrameLayout tabContent = (FrameLayout) tabHostLayout.findViewById(android.R.id.tabcontent);
// inflate tab content layouts and add to tabhost layout
LinearLayout tab1 = (LinearLayout) inflater.inflate(R.layout.dialog_tab1, null);
tabContent.addView(tab1);
LinearLayout tab2 = (LinearLayout) inflater.inflate(R.layout.dialog_tab2, null);
tabContent.addView(tab2);
// tab setup
TabHost tabHost = (TabHost) tabHostLayout.findViewById(R.id.TabHost_Dialog_Tabs);
tabHost.setup();
TabHost.TabSpec tab_1 = tabHost.newTabSpec("Category 1");
tab_1.setContent(R.id.LinearLayout_Dialog_Tab1_Content);
tab_1.setIndicator(this.getResources().getString(R.string.dialog_tab1), this.getResources().getDrawable(R.drawable.tabhost_icon_selector));
tabHost.addTab(tab_1);
TabHost.TabSpec tab_2 = tabHost.newTabSpec("Category 2");
tab_2.setContent(R.id.LinearLayout_Dialog_Tab2_Content);
tab_2.setIndicator(this.getResources().getString(R.string.dialog_tab2), this.getResources().getDrawable(R.drawable.tabhost_icon_selector));
tabHost.addTab(tab_2);
// spinner setup
final Spinner spinner_location = (Spinner) tab1.findViewById(R.id.Spinner_Dialog_Location);
String[] locationArrayVals = null;
ArrayAdapter<CharSequence> adapter_location = null;
locationArrayVals = this.getResources().getStringArray(R.array.location_array_vals);
adapter_location = ArrayAdapter.createFromResource(this, R.array.location_array_listdisplay, android.R.layout.simple_spinner_item);
adapter_location.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner_location.setAdapter(adapter_location);
// ok button
Button button_ok = (Button) tab1.findViewById(R.id.Button_Dialog_OK);
button_ok.setOnClickListener(new View.OnClickListener()
{
public void onClick(View view)
{
pref_location = spinner_location.getSelectedItemPosition();
dialog.dismiss();
}
});
// create dialog
builder.setTitle("Location")
.setView(tabHost)
.setCancelable(true);
dialog = builder.create();
}
return dialog;
}
Edit with temporary workaround
For anybody interested I managed to at least stop the crashing by subclassing spinner and overriding onDetachedFromWindow() like so:
public static class CatchingSpinner extends Spinner
{
public CatchingSpinner(Context context, AttributeSet attrs)
{
super(context, attrs);
}
#Override
protected void onDetachedFromWindow()
{
try
{
super.onDetachedFromWindow();
}
catch (IllegalArgumentException e)
{
// do whatever
}
}
}
Like I said, a workaround. Still working on a solution. :/
I found a better solution. It is actually not so hard to replace Spinner with a button that looks and behaves like Spinner (except the crash, fortunately).
<Button
android:background="#android:drawable/btn_dropdown"
android:gravity="left|center_vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
public void showSpinner() {
AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
builder.setTitle(_spinnerPrompt);
builder.setSingleChoiceItems(_spinnerOptions, _spinnerSelection,
new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialog, int item) {
_spinnerSelection = item;
_pseudoSpinner.setText(_spinnerOptions[item]);
_restoreSpinnerOnRestart = false;
dialog.dismiss();
}
});
builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
#Override
public void onCancel(DialogInterface dialog) {
_restoreSpinnerOnRestart = false;
}
});
AlertDialog alert = builder.create();
_restoreSpinnerOnRestart = true;
alert.show();
}
#Override
public Bundle onSaveInstanceState() {
Bundle state = super.onSaveInstanceState();
state.putBoolean(STATE_SPINNER_RESTORE, _restoreSpinnerOnRestart);
state.putInt(STATE_SPINNER_SELECTION, _spinnerSelection);
return state;
};
#Override
public void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
_spinnerSelection = savedInstanceState.getInt(STATE_SPINNER_SELECTION, -1);
_restoreSpinnerOnRestart = savedInstanceState.getBoolean(STATE_SPINNER_RESTORE);
_pseudoSpinner.setText(_spinnerOptions[_spinnerSelection]);
if (_restoreSpinnerOnRestart) {
showSpinner();
}
};
Ok, I think I've found your problem: dialog.dismiss();
You are doing really strange thing - you are creating dialog through activity but say dismiss to it not to activity to dismiss it - those are two different approaches which you can't mix. You should choose one method: dialog.show and dialog.dismiss or activity.showDialog() and activity.dismissDialog() <- this is better because handles orientation change.
What you should do is just remove dialog from class variables, use activity.showDialog everywhere. Probably your problem was in dismissing dialog in onClick. Just use YourActivityName.this.dismissDialog(DIALOG_NUMBER) and it should work.
Read more about dialogs form developers site - I had also such problems and taken long time to learn how it works but after all - dialogs are not so complicated ;)
I had a similar a crash. I worked around by disabling orientation changes when dialog is displayed.
#Override
public void onDismiss(DialogInterface dialog) {
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
}
#Override
public void onShow(DialogInterface dialog) {
int loadedOrientation = getResources().getConfiguration().orientation;
int requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
if (loadedOrientation == Configuration.ORIENTATION_LANDSCAPE) {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
} else if (loadedOrientation == Configuration.ORIENTATION_PORTRAIT) {
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
}
setRequestedOrientation(requestedOrientation);
}
NOTE: I actually found that there is no reliable way to lock screen orientation.