I have an activity and inside it a fragment, where i'm trying to display a gridview with pictures.
If I try to access the view in onCreateView() of the fragment, i do get a non-null handler and all is OK :
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.bar_pics_grid, container, false);
GridView gridview = (GridView) v.findViewById(R.id.gridview);
return v;
However, since i need to get data from a DB first, so I access the gridview in a custom function :
public void loadData(Bar bar) {
if (showBarDetails.getBar() != null) {
bar = showBarDetails.getBar();
GridView gridview = (GridView) getView().findViewById(R.id.gridview);
barImageArray = bar.getImages().toArray(new BarImage[bar.getImages().size()]);
gridview.setAdapter(new PicsAdapter(showBarDetails, barImageArray));
}
}
In the latter case, gridview is always null, which means this function is called before onCreateView - any ideas why is this happening ?
I initialize the Fragment in the activity's onCreate function as follows :
picsFragment = new PicsFragm(this);
Then I have an AsyncTask pulling data from the DB where I call :
#Override
protected void onPostExecute(AsyncTaskResult<Bar> result) {
if (result.getError() == null) {
sbd.setBar(result.getResult());
if (sbd.getPicsFragment().isResumed())
sbd.getPicsFragment().loadData(sbd.getBar());
}
}
Many thanks
Since your custom function is called before the onCreateView, I would suggest initializing a global variable(s) in your custom function then calling the variable(s) in your onCreateView method.
//global variable declaration like a string or data source e.g
String myValue;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.bar_pics_grid, container, false);
//Get your global variable here
String getValue = myValue;
GridView gridview = (GridView) v.findViewById(R.id.gridview);
return v;
}
public void loadData(String bar) {
//initialize your global variable here
myValue = bar;
}
The Android documentation suggests that to communicate from an activity to a hosted fragment, the fragment can define a callback interface and require that the host activity implement it. The basic pattern involves implementing onAttach in your fragment, and casting the activity to a callback inteface.
See http://developer.android.com/guide/components/fragments.html#CommunicatingWithActivity
I'd use a ReentrantLock to fix this. Create a variable in your fragment activity to hold the Database data:
private ReentrantLock dataLocker = new ReentrantLock();
private PicsAdapter adapter = null;
In loadData(Bar bar) rather than loading it directly into the grid view, load it into that variable:
if (showBarDetails.getBar() != null)
{
bar = showBarDetails.getBar();
barImageArray = bar.getImages().toArray(new BarImage[bar.getImages().size()]);
dataLocker.lock();
try
{
adapter = new PicsAdapter(showBarDetails, barImageArray);
//Check if the grid view is made (this is run after inflation)
GridView gridview = (GridView) getView().findViewById(R.id.gridview);
if (gridview != null)
{
gridview.setAdapter(adapter);
}
}
finally
{
dataLocker.unlock();
}
}
Then when you inflate your view, you make lock the dataLocker whilst you inflate. Once inflated, set the adapter if it already exists.
dataLocker.lock();
try
{
View v = inflater.inflate(R.layout.bar_pics_grid, container, false);
GridView gridview = (GridView) v.findViewById(R.id.gridview);
if (adapter != null)
gridview.setAdapter(adapter);
return v;
}
finally
{
dataLocker.unlock();
}
I'm not sure what thread you called execute on the AsyncTask on, so we use the ReentrantLock to make sure the threads aren't interfering with each other.
Related
I have a problem, whenever i try to add an eventhandler to a button i get a null reference exception, i am trying to create a popup window with a DialogFragment, where inside it im calling the view PopUpWindow wich will show up on screen, but when i try to access the buttons by id and to assign them eventhandlers for example:
Button btnCopyText = dp.view.FindViewById<Button>(Resource.Id.btnCopyText);
btnCopyText.Click += BtnCopyText_Click;
then i get a null reference exception, can anyone help me, below is the necessary code.
class dialog_Popup:DialogFragment
{
public View view;
public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
base.OnCreateView(inflater, container, savedInstanceState);
view = inflater.Inflate(Resource.Layout.PopupWindow, container, false);
return view;
}
public override void OnActivityCreated(Bundle savedInstanceState)
{
Dialog.Window.RequestFeature(WindowFeatures.NoTitle);
base.OnActivityCreated(savedInstanceState);
}
public class MainActivity : Activity
{
protected override void OnCreate(Bundle bundle)
{
//some code
}
public string itemclicked;
dialog_Popup dp;
private void Lv_ItemLongClick(object sender, AdapterView.ItemLongClickEventArgs e)
{
//View popUpView = LayoutInflater.Inflate(Resource.Layout.PopupWindow,
//null); // inflating popup layout
Button height = FindViewById<Button>(Resource.Id.btnCopyText);
//Then: change the width of the button
FragmentTransaction transaction = FragmentManager.BeginTransaction();
dp = new dialog_Popup();
dp.Show(transaction,"Popup");
itemclicked = lv.GetItemAtPosition(e.Position).ToString();
Button btnCopyText = dp.view.FindViewById<Button>(Resource.Id.btnCopyText);
btnCopyText.Click += BtnCopyText_Click;
Button btnSaveCurrentAya = dp.view.FindViewById<Button>(Resource.Id.btnSaveCurrentAya);
btnSaveCurrentAya.Click += BtnSaveCurrentAya_Click;
Button btnsavingsAya = dp.view.FindViewById<Button>(Resource.Id.savingsAya);
btnsavingsAya.Click += BtnsavingsAya_Click;*
Button btnShareFB = dp.view.FindViewById<Button>(Resource.Id.fbShare);
btnShareFB.Click += BtnShareFB_Click;
}
}
There are several reasons why a NullReferenceException can occur with FindViewById:
The layout does not contain the id -> check that the correct layout and id is inflated/ referenced
The type like Button is incorrect
In your case, check that dp and dp.view is not null.
One thing to mention here is, that it is not the best implementation to reference the control of a fragment in you main view. A fragment is something that should be able to life on her own. So I see two ways of implementing your desired behavior:
1) The fragment gets an event and you listen to that. This means your main view will contain the logic to save something.
2) The logic moves into the fragment.
I have of null object inside a fragment. The basic idea is that I have an activity that fetches a database asynchronously. However my recyclerview where I will populate the data lives into a fragment. The pseudo-code is more or less
ACTIVITY:
public void onCreate(Bundle savedInstanceState) {
//kicks off a query to the server
mData = new Gson().fromJson(getIntent().getExtras().getString(Constants.MYDATA), MyData.class);
if (mVenue == null) {
finish();
return;
}
// a bunch of stuff
// create a fragment
mMyFrag = new MyFrag();
}
public void CallBackWhenDone(final List<DataSet> dataset) {
// notify the frag that we are done
mMyFrag.notifyDataSetChanged(messages);
}
FRAGMENT:
private RecyclerView mRV;
private ParentActivity mActivity;
private ActivityAsynchData mAsynchData;
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.recycler, container, false);
mRV = (RecyclerView) view.findViewById(R.id.list);
mRV.setLayoutManager(new LinearLayoutManager(getActivity().getApplicationContext(), LinearLayoutManager.VERTICAL, false));
if (null != mActivity) {
mAsynchData = mActivity.GetAsynchData();
}
if (null != mAsynchData) {
mRV.setAdapter(new MyRecyclerAdapter(getActivity(), mAsynchData));
}
}
// mRV is null when the activity "CallBackWhenDone" calls the frag
// all private variables are gone! why?
public void notifyDataSetChanged(final List<Message> messages) {
MyRecyclerAdapter adapter = ((MyRecyclerAdapter) mRV.getAdapter());
adapter.setMessageList(messages);
adapter.notifyDataSetChanged();
}
I ended up hacking my recycler (mRV in this case) view to be static, but this looks super hacked.
any way around? In other words how can I fetch my "mRV" after the fragment has been created and the private vars are all gone.
What i could understand is, that you initialised the fragment and try to access the recycler view in that but its throwing you null. I am not surprised to see it being as null. The way you have called the method is not correct.You need to get the hosted and already running fragment, instance try doing this:
if you are using support fragment use getSupportFragmentManager instead of getFragmentManager.
MyFrag fragment = (MyFrag) getFragmentManager().findFragmentById(R.id.fragmentHolder);
fragment.<specific_function_name>();
Here , the R.id.fragmentHolder is the id of the frame layout or any layout that you are using to host your fragment inside the an activity.
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fragmentHolder"
android:layout_width="match_parent"
android:layout_height="match_parent"></FrameLayout>
I want to dynamically change my UI by checking some condition in my onCreate activity . But my app crashes .
#Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.busdetail);
Intent i=getIntent();
String s=i.getStringExtra("fname");
Log.d("ssssss: ",s);
if(s=="itemName1")
{
for(int k=0;k<itemName1.length;k++)
{
arrayk[k]=itemName1[k];
}
}
CustomList adapter = new CustomList(this, arrayk, imageId);
list = (ListView) findViewById(R.id.list);
list.setAdapter(adapter);
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
//String selectedItem = itemName[+position];
// Toast.makeText(getApplicationContext(),selectedItem,Toast.LENGTH_LONG).show();
Intent intent = new Intent(BusDetail.this, AddComment.class);
startActivity(intent);
}
});
}
Here if (s=="itemName1") then I copy itemName1 into arrayk. Both itemName1 and arrayk are string array . But when I clicked an item, my app crashes. When I avoid if condition block then my app runs well.
Two things.
First, to compare a String, don't use == ! You have to compare the value of the String, not the object itself. Your condition is wrong now... == compares the object references but not the content.
Use the following condition: if(s.equals("itemName1"))
or use equalsIgnoreCase("itemName1") if you want to ignore the case.
Then Remember that during the onCreate, your UI is not ready yet. If you have any change to do in the UI, do it in the onCreateView
//////////////////////////////////////////////////////////
onCreateView is working this way:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View mainview = inflater.inflate(R.layout.yourlayout, container, false);
//mainview is your main view, use it to find your elements, assign them listeners, change text, attributes, etc
return mainview; //you have to return this view
}
It is basically working like onCreate except that this is were you have to manage everything linked to your views and to your UI. It's also there that you have to inflate the view you need, and to return it.
I have two fragments on a view pager.
I once had to move data from fragment B to A and refresh the data displayed on A and I did it with getItemPosition.
For some reason, the same method doesn't work when I try to reset all data..
In my adapter i have :
public void refresh()
{
notifyDataSetChanged();
}
#Override
public int getItemPosition( Object obj )
{
return POSITION_NONE;
}
in fragment where I click 'reset' :
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
notTriedPasswordsList = PagerActivity.mainList;
.....
....
resetButton.setOnClickListener( new View.OnClickListener() {
#Override
public void onClick( View v )
{
PagerActivity.resetPasswords();
PagerActivity.viewPagerAdapter.refresh();
}});
viewPager activity hosting both fragments:
public static void resetPasswords()
{
mainList.addAll( 0, historyList );
historyList.clear();
PagerActivity.viewPagerAdapter.refresh();
}
Main fragment where the pass is displayed :
#Override
public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState )
{
View view = inflater.inflate(R.layout.fragment_main, container, false);
.....
nextCodeDisplay = ( TextView ) view.findViewById( R.id.passwordDisplayTextView );
nextCodeDisplay.setText( notTriedPasswordsList.get( 0 ).getPasswordString() );
....
nextButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v)
{
notTriedPasswordsList.remove( 0 );
if( notTriedPasswordsList.size() > 0 && !(notTriedPasswordsList.get( 0 ).getTried()) )
{
nextCodeDisplay.setText( notTriedPasswordsList.get( 0 ).getPasswordString() );
}
}
PagerActivity is treated like a static class, and you can only access static methods and member data and objects in this way. About code:
notTriedPasswordsList = PagerActivity.mainList;
Note: So now PagerActivity can access static mainList object, or notTriedPasswordsList (sharing the same memory). But this is the only object you can access since your code references static methods.
On code PagerActivity.viewPagerAdapter.refresh(), I am not clear on what data this refreshes since I don't see the enough code, again refresh() must be a static method. With code notifyDataSetChanged(), there must be a direct link between viewPagerAdapter and the data object, probably an ArrayList. Certainly I don't see any direct relation between the two.
Perhaps you want code like:
viewPagerAdapter pagerAdapter = new viewPagerAdapter();
This way you can have the relationship between the adapter and possibly an ArrayList object. The benefit of creating an instance with new is that it saves data and the state inside the class in the form of an object, in my sample that is pagerAdapter.
I could not suggest specific set of codes for now since I don't see sufficient amount of it for me to fix. Perhaps you can fix code first and then we all can contribute.
Your call to PagerActivity.viewPagerAdapter.refresh(); won't cause your fragment to be redrawn. Instead you should access your fragment directly and create a custom refreshUI() method in it.
public void refreshUI(){
nextCodeDisplay.setText( notTriedPasswordsList.get( 0 ).getPasswordString() );
}
I suggest to change your approach. I've uploaded a simple project to my dropbox public folder. Here you can find a reference implementation of how two fragments managed by a ViewPager can share information. The first Fragment - Fragment#1 - simply displays a String that is generated by Fragment#2. Fragment#2 has a button that, when clicked, sends a random String to Fragment#1 through the Activity. No need to refresh viewpager, no need of static methods, simple and working. I guess you can adapt this example to your needs.
As you said that you want to refresh your data, personally i would like to suggest to use swipe refresh layout. It will be very useful for this purpose and stylish as well. Following is the code.
Swipe_Refresh_layout.xml
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/swipe_container"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ListView android:id="#+id/listview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
/>
</android.support.v4.widget.SwipeRefreshLayout>
And following is the activity i am using for this layout.
public class LatestNewsFragment extends Fragment implements OnRefreshListener ,OnScrollListener{
SwipeRefreshLayout swipeLayout;
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState)
{
=
View rootView = inflater.inflate(R.layout.Swipe_Refresh_layout, container, false);
swipeLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipe_container);
swipeLayout.setOnRefreshListener(this);
swipeLayout.setColorScheme(android.R.color.holo_blue_bright,
android.R.color.holo_green_light,
android.R.color.holo_orange_light,
android.R.color.holo_red_light);
return rootView;
}
public void onRefresh() {
new Handler().postDelayed(new Runnable() {
#Override public void run() {
swipeLayout.setRefreshing(false);
additemstatus();
}
}, 5000);
}
Now in overridden refresh method you can refresh or load your data. I hope this will be very helpful for your support.
I have a weird situation with a custom ArrayAdapter.
When I try to update the adpater with new data, instead of the data being updated, the new data are inserted to the beginning of the listview and the old data are remaining and visible once you scroll the listview.
UPDATE
It seems that the problem is caused by the ArrayList from the fragment bundle.
If I don't set the listview in the onCreateView from the fragment bundle, my update code works fine, but now I'm puzzled why this:
ArrayList<Collection> cityStoresList = fragmentBundle.getParcelableArrayList("stores");
mStoresList.addAll(cityStoresList);
is causing the items to always remain on the list?
END OF UPDATE
Here are parts of the code: (Collection is a custom object model class)
ArrayList<Collection> mStoresList = new ArrayList<Collection>();
/** List Adapter */
private StoresListAdapter mListAdapter;
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
boolean attach = false;
if (container == null) {
attach = true;
}
Bundle fragmentBundle = getArguments();
ArrayList<Collection> cityStoresList = fragmentBundle.getParcelableArrayList("stores");
mStoresList.addAll(cityStoresList);
//inflater code not added here, but is present
mListAdapter = new StoresListAdapter(getActivity(), mStoresList);
mListView.setAdapter(mListAdapter);
return layout;
}
My custom adapter is as follows:
public class StoresListAdapter extends ArrayAdapter<Collection> {
public StoresListAdapter(Context c, ArrayList<Collection> array) {
super(c, 0, array);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// View from recycle
View row = convertView;
// Handle inflation
if (row == null) {
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = inflater.inflate(R.layout.row_store, null);
}
// Get the Store
Collection store = getItem(position);
//rest of code follows
return row;
}
}
Now when I want to update my adapter I use the following:
public void updateAdapter(ArrayList<Collection> storesList, final int listIndex) {
mStoresList.clear();
mStoresList.addAll(storesList);
mListAdapter.notifyDataSetChanged();
}
And this creates the issue I mentioned. The new items appear fine, but the previous ones are still visible and added after the new ones.
It's like adding the new items in the ArrayList as the first items, instead of just replacing the old ones.
Any ideas, suggestions?
Ok, finally found the problem.
Because the whole thing is within a fragment, the oncreateView is actually called when I'm attaching the array, so what happens is that my updateAdapter method is called, the items are added and displayed, before the view is actually visible.
Then the oncreateView method is fired and the original bundle items are being added to the Arraylist....