I have this NPE that is driving me crazy, maybe it's just "tunnel vision" but I can't solve it. I have a fragment inside an activity, and in the activity's onCreate() I instantiate fragment. Then later when the fragment is added to activity I call fragments method from the activity. The code is below
transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.ActionInputFragment, settingsFragment).addToBackStack(null);
transaction.commit();
if(jsonUserData != null)
settingsFragment.loadUserData(jsonUserData);
In the fragment I have:
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
this.ctx = getActivity();
edtFullName = (EditText) getActivity().findViewById(R.id.edtFullNameSettings);
edtFullName.addTextChangedListener(new TextWatcher .....);
}
Adding the textwatcher works. Below (in the same fragment) is the method whose call generates the error:
public void loadUserData(JSONObject jsonUserData) {
String fullName;
try {
fullName = String.format("%s %s", jsonUserData.getString("first_name"), jsonUserData.getString("last_name"));
} catch (JSONException e) {
fullName = "";
}
//if(edtFullName != null)
this.edtFullName.setText(fullName);
}
I have checked the layout and the editText id is there.
the problem is that this.edtFullName is not yet initialized because onActivityCreated is not called yet thus edtFullName is null upon calling setText() method
You can pass the reference of the EditText in the loadUserData instead in the onActivityCreated
Related
I have been stuck on this for awhile, I am loading a fragment from within an activity (replacing another). It loads fine and I can see it and return to the previous fragment, but when I call a method to load data into it I get a null pointer exception. My first code was this...
public class MainActivity extends Activity implements MainListFragment.MainListListener, StoryFragment.StoryListener {
MainListFragment mainListFragment;
StoryFragment storyFragment;
.
.
.
public void onMainListClick(DBRecordType recordType, int recordID){
switch(recordType){
case story:{
DBHandler dbHandler = new DBHandler(this, null, null, 1);
Story story = dbHandler.getStory(recordID);
Toast.makeText(getApplicationContext(), story.toString(), Toast.LENGTH_SHORT ).show();
storyFragment = new StoryFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.container, storyFragment);
transaction.addToBackStack(null);
transaction.commit();
storyFragment.loadStory(story);
break;
}
I am getting this error:
java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.EditText.setText(java.lang.CharSequence)' on a null object reference
at com.maniblett.listtest.StoryFragment.loadStory(StoryFragment.java:60)
at com.maniblett.listtest.MainActivity.onMainListClick(MainActivity.java:104)
The method in the fragment is setting text in an edittext, here's the fragment method I am calling mName and mSummary are references to edit text widgets :
public void loadStory(Story story){
mName.setText(story.get_name());
mSummary.setText(story.get_summary());
}
Is it legal to refer to a fragment like this (calling a method on the same reference I used to add the fragment via the fragment manager)? It seems like I already have a reference to the fragment, so using find fragment by ID would be redundant but when I double checked everything I read seemed to indicate I needed to find the fragment using findFragmentByID or Tag first so changed my code to this...
storyFragment = new StoryFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.container, storyFragment, "loadedFragment");
transaction.addToBackStack(null);
transaction.commit();
StoryFragment loadedFragment = (StoryFragment)getFragmentManager().findFragmentByTag("loadedFragment");
loadedFragment.loadStory(story);
break;
But I get a similar error, which is:
java.lang.NullPointerException: Attempt to invoke virtual method 'void com.maniblett.listtest.StoryFragment.loadStory(com.maniblett.listtest.datamodel.Story)' on a null object reference
at com.maniblett.listtest.MainActivity.onMainListClick(MainActivity.java:107)
at com.maniblett.listtest.MainListFragment.onListItemClick(MainListFragment.java:156)
at android.app.ListFragment$2.onItemClick(ListFragment.java:160)
at android.widget.AdapterView.performItemClick(AdapterView.java:300)
I have verified the object I am sending is not null after creating it (the 'story' in the case statement is an enum). Again, the frgament loads and runs fine if I comment out the method call, it is just when I call the emthod on it it fails. so, I guess I don't have the actual fragment that is loaded? Can someone tell me what I'm doing wrong? (I did search many other similar topics but couldn't find anything which helped, I'm pretty new to android). Thanks to any who take the time to help!
StoryFragment class:
public class StoryFragment extends Fragment
{
StoryListener activityCallback;
private EditText mName;
private EditText mSummary;
public interface StoryListener {
public void onAddButtonClick(String text);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final View rootView = inflater.inflate(R.layout.fragment_story, container, false);
Button button = (Button)rootView.findViewById(R.id.button);
mName = (EditText)rootView.findViewById(R.id.name);
mSummary = (EditText)rootView.findViewById(R.id.summary);
button.setOnClickListener(new Button.OnClickListener() {
public void onClick(View v) {
DBHandler dbHandler = new DBHandler(getActivity().getBaseContext(), null, null, 1);
Story story = new Story(mName.getText().toString(),mSummary.getText().toString());
dbHandler.addStory(story);
activityCallback.onAddButtonClick("Story"); //use same callback for all record types passing back record type created?
}
} );
return rootView;
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try { activityCallback = (StoryListener) activity;
}
catch (ClassCastException e) {
throw new ClassCastException(activity.toString()+ " must implement StoryListener");
}
}
public void loadStory(Story story){
mName.setText(story.get_name());
mSummary.setText(story.get_summary());
}
}
Well, you should understand how android works in the first place. The onCreateView method gets called only when the fragment is going to come visible. So when you call loadstory immediately after the FragmentTransaction method its obvious you'll get an NullPointer Exception
My solution:
Declare two variables in the StoryFragment as name and summary
Change the loadStory method is like this
public void loadStory(Story story){
this.name = story.get_name();
this.summary = story.get_summary();
}
Finally in the OnCreateView method of StoryFragment after change here appropriately
mName = (EditText)rootView.findViewById(R.id.name);
mSummary = (EditText)rootView.findViewById(R.id.summary);
//if your fragment is also gonna be called by some other manner you should check for null in this.name and this.summary before setting it to the `TextView`
mName.setText(this.name);
mSummary.setText(this.summary);
Thank you. That worked perfectly. Actually, what I did was create a private Story type and then in StoryFragment.loadStory I assigned it.
public void loadStory(Story story){
mStory = story;
}
I also was able to then see that it works fine to not have to search for the fragment using findFragmentByID, this part worked:
storyFragment = new StoryFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.container, storyFragment);
transaction.addToBackStack(null);
transaction.commit();
storyFragment.loadStory(story);
Thanks very much for pointing me in the right direction here
I have an Activity that dynamically adds two fragments. One is a hidden (no view) Fragment that has setRetainInstance(true) and handles the interface to my Database Handler. Its purpose is to start the AsyncTask for getting data out of the database and listen for the Database Handler to give its results back. It will then hand the data back to the Activity via another listener. The Activity will then hand the data to the display Fragment which has a ListView within and will display accordingly.
Activity: NOT a FRAGMENT ACTIVITY
import android.app.Activity;
import android.os.Bundle;
public class Workout_Search_Display_Activity extends Activity {
private final String search_string = "SEARCH_STRING";
private final String search_type = "SEARCH_TYPE";
private String Search_String = "";
private String Search_Type = "";
private Workout_Search_Holder_Fragment SearchHolder;
private Workout_Search_Display_Fragment search_display_fragment;
private Workout_Search_Activity_Listener WSAL;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Search_String = getIntent().getStringExtra(search_string);
Search_Type = getIntent().getStringExtra(search_type);
if (getFragmentManager().findFragmentById(android.R.id.content) == null) {
SearchHolder = Workout_Search_Holder_Fragment.newInstance(Search_String, Search_Type);
getFragmentManager().beginTransaction()
.add(android.R.id.content, SearchHolder).commit();
search_display_fragment = Workout_Search_Display_Fragment.newInstance();
getFragmentManager().beginTransaction()
.add(android.R.id.content, search_display_fragment).commit;
} //added too try to fix// else
//added to try to fix// SearchHolder = (Workout_Search_Holder_Fragment) getFragmentManager.findFragmentByTag("search_holder");
WSAL = new Workout_Search_Activity_Listener() {
public void NothingFound() {
search_display_fragment.no_data();
}
public void results_found(ArrayList<Search_Results_Holder> results) {
search_display_fragment.is_data();
search_display_fragment.handover_data(results);
}
};
SearchHolder.setListener(WSAL);
}
}
Fragment:
import android.app.Fragment;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
public class Workout_Search_Holder_Fragment extends Fragment implements DatabaseHelper.Workout_Search_Listener {
private String Search_String = "";
private String Search_Type = "";
private final static String search_string = "SEARCH_STRING";
private final static String search_type = "SEARCH_TYPE";
private Workout_Search_Activity_Listener listener;
protected static Workout_Search_Holder_Fragment newInstance(
String Search_String, String Search_Type) {
Workout_Search_Holder_Fragment f = new Workout_Search_Holder_Fragment();
Bundle args = new Bundle();
args.putString(search_string, Search_String);
args.putString(search_type, Search_Type);
f.setArguments(args);
return f;
}
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setRetainInstance(true);
Search_String = getArguments().getString(search_string, null);
Search_Type = getArguments().getString(search_type, null);
sendSearch();
}
public Workout_Search_Activity_Listener getListener() {
return listener;
}
public void setListener(Workout_Search_Activity_Listener listener) {
this.listener = listener;
}
private void sendSearch() {
DatabaseHelper.getInstance(getActivity()).getSearchResultsAsync(
Search_String, Search_Type, this);
}
static public <T> void executeAsyncTask(AsyncTask<T, ?, ?> task, T... params) {
if (Build.VERSION.SDK_INT > +Build.VERSION_CODES.HONEYCOMB) {
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
} else {
task.execute(params);
}
}
#Override
public void return_no_results_found() {
listener.NothingFound();
}
#Override
public void return_search_results(ArrayList<Search_Results_Holder> results) {
Log.v("workout search holder fragment", "results found in fragment, handing off to activity");
listener.results_found(results);
}
}
My issue is: When rotating the screen, my code crashes with a Null Pointer Exception on the SetListener for the Search_Hander in the Activity. If I change the Activity to a FragmentActivity and use the SupportFragmentManager....none of this is an issue...all works correctly with just those quick changes (something about the now-deprecated onRetainNonConfigurationInstance() being overridden by the SupportFragmentManager and it handles everything for you).
In trying to fix this, I kept it as an Activity, but put in SearchHolder = (Workout_Search_Holder_Fragment) getFragmentManager.findFragmentByTag("search_holder") as part of the else if the R.id.Content wasn't null and gave the Fragment a tag when I initially set it up if the R.id.Content WAS null. See the commented out code above. This worked, but created another issue where the onActivityCreated started again and launched my AsyncTask when I didn't want it to. I can't send or check variables in the savedInstanceState bundle to the fragment, as it is null due to the setRetainInstance(true). I know I'm doing something wrong, but can't get around it.
You are using
add(ANDROID.R.id.content, SearchHolder)
while adding your fragment, instead try
add(ContainerID, SearchHolder,"search_holder"), which makes sure that your fragment is added with a tag "search_holder"
then as you did in else part try getting that fragment object by calling
findFragmentByTag()
method
Hope this helps!!!
Answer is two fold:
I needed to manually attach the Fragments, this I knew.
I was executing my AsyncTask in the wrong area. This I didn't know.
It turns out that my AsyncTask was re-executing as OnActivityCreated is called again, even if saveInstanceState(true) is set. BUT, onCreate is not. By just changing the calling method to onCreate, i ensured that the AsyncTask was called only once, as it won't be called again upon Activity recreation. In the Activity, I still needed to reset my holders for both fragments upon testing if R.id.content was NOT null by grabbing those fragments that matched the tags...and this was part of the answer that Dinash gave.
if (getFragmentManager().findFragmentById(android.R.id.content) == null) {
SearchHolder = Workout_Search_Holder_Fragment.newInstance(
Search_String, Search_Type, false);
getFragmentManager().beginTransaction()
.add(android.R.id.content, SearchHolder, "searchholder")
.commit();
search_display_fragment = Workout_Search_Display_Fragment
.newInstance();
getFragmentManager()
.beginTransaction()
.add(android.R.id.content, search_display_fragment,
"displayholder").commit();
} else {
Log.v("activity", "non-null holder");
SearchHolder = (Workout_Search_Holder_Fragment) getFragmentManager()
.findFragmentByTag("searchholder");
search_display_fragment = (Workout_Search_Display_Fragment) getFragmentManager()
.findFragmentByTag("displayholder");
}
FragmentManager required me to test for the content being null in addition to reattaching the Fragments on my own, which is something that the FragmentSupportManager did NOT required. This also has the effect of requireing me to save the ArrayList that I am using for the ListView...again...FragmentSupportManager did all this on its own without prodding from me.
I still am curious as to why this behavior is different for the FragmentActivity as it seems to take care of ALL of this for me and required minimum amount of effort/code to work.
I have main activity which embeds fragment:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
vd = VirtualDatabaseTableProvider.getInstance(getApplicationContext());
fm = getSupportFragmentManager();
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
//Create Layout and add fragments
setContentView(R.layout.main_window);
ListFragment ListFragment= new ListFragment();
android.support.v4.app.FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment_pane, ListFragment, "List");
//ft.replace(R.id.fragment_pane, ListFragment);
ft.addToBackStack(null);
ft.commit();
//Initialising buttons
imgBtnFontInc = (ImageButton) findViewById(R.id.ImgBtnUpFont);
imgBtnFontInc.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(textViewAttached){
try{
//Some text Resize
}
}catch (NullPointerException npe){
Log.e(TAG, "Error on calling Text resize");
Log.e(TAG, npe.getMessage());
Log.e(TAG, npe.getStackTrace().toString());
}
}
}
}
);
/* More Buttons code..... */
imgBtnFontDec.setVisibility(View.GONE);
imgBtnFontInc.setVisibility(View.GONE);
/* Some Saved State handling to recover detailed text Screen*/
if(savedInstanceState != null){
if (savedInstanceState.containsKey("UUID")){
try{
String uuid = savedInstanceState.getString("UUID");
if (uuid != null){
iniTextScreen(uuid);
}
}catch (Exception e){
Log.e(TAG, "Unable To return text");
}
}
}
Text is initialised with function:
private void initTextScreen(String StringID){
Bundle Data = new Bundle();
Data.putString("UUID", StringID);
TextScreenFragment TextFragment = new TextScreenFragment();
TextFragment.setArg1ments(Data);
if(fm == null){
fm = getSupportFragmentManager();
}
android.support.v4.app.FragmentTransaction ft = fm.beginTransaction();
ft.setCustomAnimations( R.anim.animation_enter, R.anim.animation_exit);
ft.replace(R.id.fragment_pane, TextFragment, "TextFragment");
ft.addToBackStack(null);
ft.commit();
}
I handled Buttons visibility in main activity with simple Callback from TextScreenFragment. Callback in main activity:
public void onTextViewAttached() {
textViewAttached = true;
MainActivity.this.imgBtnFontDec.setVisibility(View.VISIBLE);
MainActivity.this.imgBtnFontInc.setVisibility(View.VISIBLE);
}
Callback called in TextScreenFragment:
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (!(activity instanceof Callbacks)) {
throw new IllegalStateException(
"Activity must implement fragment's callbacks.");
} else {
listener = (Callbacks) activity;
listener.onTextViewAttached();
}
}
public interface Callbacks {
/**
* Callback for when an item has been selected.
*/
public void onTextViewAttached();
}
It works, however when I put android phone is switch potrait/landscape mode: onAttached in fragment get called way before onCreate in main Activity and Button objects.
How can fragment be attached on main activity before even onCreate is called in main activity?
I attached a particular fragment at very end of onCreate method after buttons were already initialized,but why onAttach in fragment is called before even I attach fragment and get null exception, because button objects were not initialized in onCreate? How it is even possible?
When I comment out:
`// MainActivity.this.imgBtnFontDec.setVisibility(View.VISIBLE);
//MainActivity.this.imgBtnFontInc.setVisibility(View.VISIBLE);`
in callback function public void onTextViewAttached(), no more crashes, but still I noticed that onAttach is called before main activity created and is called twice:
one time with uninitialised activity from hell knows were (every element of main activity is either null or has default values), second time when fragment is properly attached from main activity onCreate.
I made conclusion that on orientation switch fragments gets atached to uninitialised activity. Am I missing something, on orientation change should I call some other function before onCreate in main activity to get buttons from layout?
Is it some kind of fragment automated attach behaviour which I am not aware of and I could take advantage of?
What is life cycle of activity with fragment attached to it, because onAttach called in fragment before even main activity is created seems counter intuitive.
I've had a look around and found a couple of questions with a similar topic, but nothing that helped in my case.
I'm trying to access an existing active fragment using getSupportFragmentManager().findFragmentByTag(TAG), but it always returns null. Replies on similar questions suggested that it takes a while for the commit to be executed, so calling findFragmentByTag would return null if called too early. I've tried two things:
add getSupportFragmentManager().executePendingTransactions()
immediately after the commit, but still get null.
added a button... pressing this after the activity has been created,
the fragment registered and the view displayed should leave the
system enough time to commit. But i still get null.
Here's my activity:
public class MainActivity extends ActionBarActivity {
private static final String F_SETTINGS = "f_settings";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn = (Button) findViewById(R.id.btn);
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
debug();
}
});
if (savedInstanceState == null) {
FSettings newFragment = new FSettings();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.container, newFragment);
ft.addToBackStack(F_SETTINGS);
ft.commit();
// getSupportFragmentManager().executePendingTransactions();
//// Activating this did not make any difference...
}
debug();
}
private void debug() {
String txt = "null";
Fragment frag = getSupportFragmentManager().findFragmentByTag(F_SETTINGS);
if (frag != null) {
txt = frag.toString();
}
Log.i("Testing", txt);
}
}
What am I doing wrong here?
Cheers,
Max
In your code you haven't mentioned tag in replace method So,
Use this structure of replace method of fragment
ft.replace(R.id.container, newFragment,"fragment_tag_String");
Refer this link for more information. fragment replace with tag name
I have a fragment which is basically a list view. The parent activity calls a method to retrieve a list of roster items from a service. When the data returns from the service I call updateRosterItems on the fragment passing through and ArrayList of Roster items. The problem is that it works the first time through, but then when I select a different tab, and then come back to the tab with the fragment, the getActivity() returns null and I can't hook up the data to the ArrayAdapter.
This is the code for the updateRosterItems function:
public void updateRosterList(ArrayList<RosterInfo> rosterItems)
{
if(_adapter == null)
{
_adapter = new RosterItemAdapter(getActivity(), R.layout.roster_listview_item, rosterItems);
}
Activity activity = getActivity();
if(activity != null)
{
ListView list = (ListView)activity.findViewById(R.id.lstRosterItems);
list.setAdapter(_adapter);
_adapter.notifyDataSetChanged();
}
}
I've read about similar issues caused by code being called before the fragment is attached. I guess my question is, is there a way to delay the call to the updateRosterList until after the onAttach is called? The solution I'm toying with is that if getActivity() returns null then store the data in private variable in the fragment, and in the onAttach method check if there is data in the varialbe and then call the update on the adapter. This seems a bit hacky though. Any ideas?
UPDATE: I've managed to get it working by doing this. I'm quite new to Android development and it seems a bit hacky to me as a solution. Is there a better way? Basically the updateRosterList function is the one that is called from outside of the fragment.
public class RosterListFragment extends Fragment {
RosterItemAdapter _adapter = null;
private ArrayList<RosterInfo> _items;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
return inflater.inflate(R.layout.roster_listview, container, false);
}
#Override
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
if(_items != null)
{
performUpdateRosterList(_items);
}
}
public void updateRosterList(ArrayList<RosterInfo> rosterItems)
{
Activity activity = getActivity();
if(activity != null)
{
performUpdateRosterList(rosterItems);
}
else
{
_items = rosterItems;
}
}
private void performUpdateRosterList(ArrayList<RosterInfo> rosterItems)
{
Activity activity = getActivity();
if(_adapter == null)
{
_adapter = new RosterItemAdapter(activity, R.layout.roster_listview_item, rosterItems);
}
ListView list = (ListView)activity.findViewById(R.id.lstRosterItems);
list.setAdapter(_adapter);
_adapter.notifyDataSetChanged();
}
}
You are correct, the activity isn't yet attached. There's two ways to handle this.
Don't make the changes until after the activity has been attached. Perhaps just save off rosterItems, and have it updated later.
Pass in the context into your updater function.
Personally, I would say the first is probably be better path, but either one could work fine.