I've figured out that in defined concatenation of circumstances RecyclerView leads to memory leak. To archive such effect, I have created FragmentPagerAdapter that contains fragment with RecyclerView as child.
In case of going application to background or finishing, leak canary fires memory leak alert. Here's my activity class
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ViewPager mViewPager = (ViewPager) findViewById(R.id.view_pager);
List<Fragment> fragments = new LinkedList<>();
for (int i = 0; i < 10; i++) {
fragments.add(CustomFragment.newInstance(i));
}
mViewPager.setAdapter(new CustomPagerAdapter(getFragmentManager(), fragments));
mViewPager.setCurrentItem(0, false);
}
public class CustomPagerAdapter extends FragmentPagerAdapter {
private List<Fragment> fragments;
public CustomPagerAdapter(FragmentManager fragmentManager, List<Fragment> fragments) {
super(fragmentManager);
this.fragments = fragments;
}
#Override
public Fragment getItem(int position) {
return fragments.get(position);
}
#Override
public int getCount() {
return fragments.size();
}
}
}
To get the leak, RecyclerView even doesn't need to be initialized. If I comment it in xml file, the leak isn't fired
public class CustomFragment extends Fragment {
public static final String POSITION = "position";
public static CustomFragment newInstance(int position) {
Bundle b = new Bundle();
b.putInt(POSITION, position);
CustomFragment fragment = new CustomFragment();
fragment.setArguments(b);
return fragment;
}
private int position;
private RecyclerView recyclerView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
position = getArguments().getInt(POSITION);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_custom, container, false);
TextView textView = (TextView) v.findViewById(R.id.text);
textView.setText("Position: "+position);
recyclerView = (RecyclerView) v.findViewById(R.id.pulse_recyclerview);
return v;
}
#Override
public void onDestroy() {
super.onDestroy();
RefWatcher refWatcher = CustomApplication.getRefWatcher(getActivity());
refWatcher.watch(this);
}
}
Here's leak trace. RecyclerView version 23.1.1
In com.example.gabin.sampleapplication:1.0:1.
* LEAK CAN BE IGNORED.
* com.example.gabin.sampleapplication.MainActivity has leaked:
* GC ROOT static android.view.inputmethod.InputMethodManager.sInstance
* references android.view.inputmethod.InputMethodManager.mCurRootView
* references com.android.internal.policy.impl.PhoneWindow$DecorView.mContext
* leaks com.example.gabin.sampleapplication.MainActivity instance
* Retaining: 3,9 КБ.
* Reference Key: 67c5e9f4-464e-40c3-b21d-d802fe64a84b
* Device: LGE google Nexus 4 occam
* Android Version: 5.1.1 API: 22 LeakCanary: 1.4-beta1 02804f3
* Durations: watch=5035ms, gc=190ms, heap dump=5948ms, analysis=46384ms
May it be an Android bug?
Please, give any idea how to fix the leak, or help me figure out the reason of its firing by leakcanary.
Your stack trace shows at least two things:
It is not RecyclerView who leaks activity. It is the ImputMethodManager.
LEAK CAN BE IGNORED. According to Leak Canary docs it is a known sdk problem. But I don't think its really an activity leak. If you check your memory dump you will not see several activity instances. Code looks pretty safe at now.
Related
I have a HostActivity that uses ViewPager and TabLayout to switch between multiple Fragments. When I switch between the tabs, the Fragments instance does get the updated data. I also see the updated data in onCreateView of the Fragment instance, but the TextView.setText does not get updated. When I check the visibility of Fragment, it always shows Invisible. How do I make the fragment visible when I switch tabs so that the view gets updated with new data? Is there something missing in the Fragment/Activity Lifecycle? I am implementing ViewPager for the first time so it will be helpful to know if I am missing something.
Fragment Class:
public class StepFragment extends Fragment { #Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if (getArguments() != null) {
step = getArguments().getParcelable(SELECTED_STEP);
mDescription = step.getDescription();
}
View view = inflater.inflate(R.layout.step_fragment, container, false);
ButterKnife.bind(this,view);
Log.e(TAG, "onCreateView: "+mDescription); **// THIS GETS UPDATED DATA**
tvStepDescription.setText(mDescription);
}
return view;
}
}
Here is my Host Activity:
public class StepActivity extends AppCompatActivity {
...
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_step);
fragmentSelectAdapter = new StepFragmentSelectAdapter(getSupportFragmentManager(),this,steps,recipe);
mViewPager.setAdapter(fragmentSelectAdapter);
mTabLayout.setupWithViewPager(mViewPager);
stepFragment = (StepFragment) getSupportFragmentManager().findFragmentById(R.id.step_container);
if(stepFragment == null) {
stepFragment = StepFragment.newInstance(step, recipe);
stepFragment.setArguments(bundle);
getSupportFragmentManager().beginTransaction()
.add(R.id.step_container, stepFragment)
.commit();
} else {
stepFragment = StepFragment.newInstance(step, recipe);
stepFragment.setArguments(bundle);
getSupportFragmentManager().beginTransaction()
.replace(R.id.step_container, stepFragment)
.commit();
}
}
}
Here is my FragmentPagerAdapter, which seems to be getting the correct data as per the tab position in getItem method:
public class StepFragmentSelectAdapter extends FragmentPagerAdapter {
...
#Override
public Fragment getItem(int position) {
**// THIS GETS UPDATED DATA**
Log.e(TAG, "getItem: \nDecr: "+steps.get(position).getDescription()+"\nVideo: "+steps.get(position).getVideoURL()+"\nImage: "+steps.get(position).getThumbnailURL());
return StepFragment.newInstance(steps.get(position),recipe);
}
#Override
public int getCount() {
if (steps == null){
return 0;
}
return steps.size();
}
...
}
As far as I could understand about the problem that you are having there, I think you should implement an onResume function in your StepFragment which will get the updated data from some source and will display this in the TextView. However, I can think of a potential problem in your StepFragmentSelectAdapter. You are creating a new instance each time you are switching the tabs.
You should have the Fragment instances created before and if you are about to pass the data among fragments, you might consider having a BroadcasReceiver or listener function by implementing an interface.
So the PagerAdapter should look something like this.
public class StepFragmentSelectAdapter extends FragmentPagerAdapter {
public ArrayList<StepFragment> stepFragments;
public StepFragmentSelectAdapter(ArrayList<Step> steps) {
stepFragments = new ArrayList<>();
for(int i = 0; i < steps.size(); i++) {
stepFragments.add(StepFragment.newInstance(steps.get(position),recipe));
}
}
...
#Override
public Fragment getItem(int position) {
**// THIS GETS UPDATED DATA**
Log.e(TAG, "getItem: \nDecr: "+steps.get(position).getDescription()+"\nVideo: "+steps.get(position).getVideoURL()+"\nImage: "+steps.get(position).getThumbnailURL());
return stepFragments.get(position);
}
#Override
public int getCount() {
if (steps == null){
return 0;
}
return steps.size();
}
...
}
Thanks for the hint. I got around this problem by replacing ActionBar with a custom ToolBar with back ImageButton and using click listener to get back to the calling activity.
backButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
getActivity().onBackPressed();
}
});
I've created activity from TabbedActivity template (in Android Studio):
This template uses Fragment and FragmentPagerAdapter to handle tabs.
This is code of two classes, PlaceholderFragment extends Fragment, and SectionsPagerAdapter extends FragmentPagerAdapter:
public static class PlaceholderFragment extends Fragment {
/**
* The fragment argument representing the section number for this
* fragment.
*/
private static final String ARG_SECTION_NUMBER = "section_number";
public PlaceholderFragment() {
}
/**
* Returns a new instance of this fragment for the given section
* number.
*/
public static PlaceholderFragment newInstance(int sectionNumber) {
PlaceholderFragment fragment = new PlaceholderFragment();
Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, sectionNumber);
fragment.setArguments(args);
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_list_view, container, false);
int scnum = getArguments().getInt(ARG_SECTION_NUMBER);
final ListView listView = (ListView) rootView.findViewById(R.id.products_list);
// here refresh my listView trough internet
return rootView;
}
}
/**
* A {#link FragmentPagerAdapter} that returns a fragment corresponding to
* one of the sections/tabs/pages.
*/
public class SectionsPagerAdapter extends FragmentPagerAdapter {
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
// getItem is called to instantiate the fragment for the given page.
// Return a PlaceholderFragment (defined as a static inner class below).
return PlaceholderFragment.newInstance(position + 1);
}
#Override
public int getCount() {
// Show 3 total pages.
return 3;
}
}
In onCreateView method I refresh my ListView. This method is called when i switch tabs.
But I need to refresh ListView not when user switch tabs, but:
1. every 10 seconds
2. when user comes back to this activity (onRestart() method is called)
I have no idea how to access my ListView outside onCreateView method.
I've already tried many solutions from the internet to call onCreateView method from onRestart method, and just to access my ListView from onRestart(). None of them worked.
Globally in Activity class I have an instance of SectionsPagerAdapter and ViewPager:
private SectionsPagerAdapter mSectionsPagerAdapter;
private ViewPager mViewPager;
They are initalized in onCreate by this way:
mSectionsPagerAdapter = new SectionsPagerAdapter(getSupportFragmentManager());
// Set up the ViewPager with the sections adapter.
mViewPager = (ViewPager) findViewById(R.id.container);
mViewPager.setAdapter(mSectionsPagerAdapter);
TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
mViewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
tabLayout.addOnTabSelectedListener(new TabLayout.ViewPagerOnTabSelectedListener(mViewPager));
I think, there are 2 possible solutions, access my Fragment's view or just recall onCreateView.
I've already tried to do:
This one just doesn't work:
#Override
protected void onRestart() {
super.onRestart();
mSectionsPagerAdapter.notifyDataSetChanged();
}
This one crashes app because of NullPointerException:
#Override
protected void onRestart() {
super.onRestart();
Fragment f = mSectionsPagerAdapter.getItem(0); // tab with my list view is first tab. ( getItem(1) also does not work )
View v = f.getView(); // v is null here, however f isn't.
final ListView listView = v.findViewById(R.id.products_list); // v is null, NullPointerException is thrown.
// here refresh my listView trough internet
}
EDIT:
Since I started using Kotlin, answer based on that language will be also accepted. However, I am new to Kotlin, so more detailed answer would be needed then.
So you have a list in a Fragment that is inside a ViewPager. Ther are a few ways to solve your problem, I'd suggest you use the first one, because it is the simplest.
Fragment have lifecycle callbacks that are triggered together with lifecycle callbacks of Activity that contains that fragment. It covers most of the methods, but unfortunately not the onRestart that you are looking for. But that's not a big problem. Actually, you almost never need onRestart because you have onStart method, that method is accessible from both Fragments and Activities. It is called every time activity is restarted + the very first time activity is started. And as we can see it is exactly what you need. So to have your list updated every time, just remove the update code from the onCreate method and put it into onStart method of the Fragment.
public static class PlaceholderFragment extends Fragment {
private static final String ARG_SECTION_NUMBER = "section_number";
private ListView listView = null;
public PlaceholderFragment() {
}
public static PlaceholderFragment newInstance(int sectionNumber) {
PlaceholderFragment fragment = new PlaceholderFragment();
Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, sectionNumber);
fragment.setArguments(args);
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_list_view, container, false);
int scnum = getArguments().getInt(ARG_SECTION_NUMBER);
listView = (ListView) rootView.findViewById(R.id.products_list);
return rootView;
}
#Override
public void onStart() {
// here refresh your listView trough internet using the listView class field
}
}
Another option would be to get a reference to your fragment inside the activity. The solution I usually use is to retain a reference to the fragment inside the adapter. It is similar to the second option in your question. The only problem with your solution is that mSectionsPagerAdapter.getItem(0); always create a new fragment, while you need to get a reference to already existing fragment.
public class SectionsPagerAdapter extends FragmentPagerAdapter {
private PlaceholderFragment fragmentZero = null;
public SectionsPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
PlaceholderFragment tabFragment = PlaceholderFragment.newInstance(position + 1);
if (position == 0) {
fragmentZero = tabFragment;
}
return tabFragment;
}
#Override
public int getCount() {
return 3;
}
public PlaceholderFragment getFragmentZero() {
return fragmentZero;
}
}
You also need to move your list update logic to a separate method in the PlaceholderFragment:
private ListView listView = null;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_list_view, container, false);
int scnum = getArguments().getInt(ARG_SECTION_NUMBER);
listView = (ListView) rootView.findViewById(R.id.products_list);
refreshListView();
return rootView;
}
public void refreshListView() {
// here refresh your listView trough internet
}
At this point you can do:
#Override
protected void onRestart() {
super.onRestart();
Fragment listFragment = mSectionsPagerAdapter.getFragmentZero();
listFragment.refreshListView();
}
Whenever you have a fragment inside an activity, in a ViewPager or not, you can get a reference to the fragment using FragmentManager. Check this for details regarding the ViewPager option.
All the code above is rather straightforward and would not require any special Kotlin idioms if you'd prefer to implement it in Kotlin. You can start with converting your classes in the android studio. To do it:
Navigate to your java class
Press Control + Shift + A on Windows or Command + Shift + A on Mac
Search for Convert Java File to Kotlin File
Apply
And you are good to go with the Kotlin code of given classes. The only thing that might be quite different from java is handling ARG_SECTION_NUMBER since Kotlin does not have static fields and use Companion Objects instead.
MainActivity class:
/* all necessary imports */
public class MainActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener {
/* Other variable initialized here... */
FragOne fo;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
fo.setTextViewText("This is added from Activity");
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.addDrawerListener(toggle);
toggle.syncState();
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);
viewPager = (ViewPager) findViewById(R.id.viewpager);
setupViewPager(viewPager);
tabLayout = (TabLayout) findViewById(R.id.tabs);
tabLayout.setupWithViewPager(viewPager);
}
private void setupViewPager(ViewPager viewPager) {
ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
adapter.addFragment(new FragOne(), "My Tracker");
adapter.addFragment(new FragTwo(), "Team Tracker");
viewPager.setAdapter(adapter);
}
class ViewPagerAdapter extends FragmentPagerAdapter {
private final List<Fragment> mFragmentList = new ArrayList<>();
private final List<String> mFragmentTitleList = new ArrayList<>();
public ViewPagerAdapter(FragmentManager manager) {
super(manager);
}
#Override
public Fragment getItem(int position) {
return mFragmentList.get(position);
}
#Override
public int getCount() {
return mFragmentList.size();
}
public void addFragment(Fragment fragment, String title) {
mFragmentList.add(fragment);
mFragmentTitleList.add(title);
}
#Override
public CharSequence getPageTitle(int position) {
return mFragmentTitleList.get(position);
}
}
#Override
public void onBackPressed() {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}
}
#SuppressWarnings("StatementWithEmptyBody")
#Override
public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here.
int id = item.getItemId();
if (id == R.id.nav_manage) {
} else if (id == R.id.nav_share) {
} else if (id == R.id.nav_send) {
}
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
return true;
}
}
Fragment class:
/* all necessary imports */
public class FragOne extends Fragment {
TextView tvCName;
public FragOne() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_frag_one, container, false);
return view;
//return inflater.inflate(R.layout.fragment_frag_one, container, false);
}
#Override
public void onViewCreated(View view , Bundle savedInstanceState) {
tvCName = (TextView) view.findViewById(R.id.tvctq);
}
public void setTextViewText(String value){
tvCName.setText(value);
}
}
Fragment XML Layout:
<FrameLayout 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="com.mytip.FragOne">
<TextView
android:text="TextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/tvctq" />
</FrameLayout>
I am trying to access the TextView inside the Fragment from MainActivity like this:
FragOne fo;
fo.setTextViewText("This is added from Activity");
I keep getting a NullPointerExceptionError. I looked at all the articles to see how to access, however none of them helped me.
Can someone please let me know what am I doing wrong and how to fix it?
I also plan on adding other Views inside my Fragment that I would need to access in the future.
Because fo hasn't been initialized in the following code snippet:
FragOne fo;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
fo.setTextViewText("This is added from Activity");
...
}
fo.setTextViewText() reasonably throws NPE.
You have to pay attention to the Activity lifecycle - you seem to be setting everything up correctly, but making a few mistakes accessing the correct instance of the fragment at the time it's actually ready. Things you should do
Get proper instance of the fragment from your ViewPager, like #ginomempin suggested;
Only try to set your text no earlier then your activities onStart method has been called - I usually do it onResume method (you can override it if you haven't already). Doing it in onResume method in the activity makes sure your Fragment has already gone through it's lifecycle up till onResume as well, and data will refresh if it has been brought to the background previously.
Here's a lifecycle diagram for your reference:
You need to use your Fragment factory method when creating your Fragment in your activity. Please see below:
**
Back Stack
**
The transaction in which fragments are modified can be placed on an internal back-stack of the owning activity. When the user presses back in the activity, any transactions on the back stack are popped off before the activity itself is finished.
For example, consider this simple fragment that is instantiated with an integer argument and displays that in a TextView in its UI:
public static class CountingFragment extends Fragment {
int mNum;
/**
* Create a new instance of CountingFragment, providing "num"
* as an argument.
*/
static CountingFragment newInstance(int num) {
CountingFragment f = new CountingFragment();
// Supply num input as an argument.
Bundle args = new Bundle();
args.putInt("num", num);
f.setArguments(args);
return f;
}
/**
* When creating, retrieve this instance's number from its arguments.
*/
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mNum = getArguments() != null ? getArguments().getInt("num") : 1;
}
/**
* The Fragment's UI is just a simple text view showing its
* instance number.
*/
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.hello_world, container, false);
View tv = v.findViewById(R.id.text);
((TextView)tv).setText("Fragment #" + mNum);
tv.setBackgroundDrawable(getResources().getDrawable(android.R.drawable.gallery_thumb));
return v;
}
}
A function that creates a new instance of the fragment, replacing whatever current fragment instance is being shown and pushing that change on to the back stack could be written as:
void addFragmentToStack() {
mStackLevel++;
// Instantiate a new fragment.
Fragment newFragment = CountingFragment.newInstance(mStackLevel);
// Add the fragment to the activity, pushing this transaction
// on to the back stack.
FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.simple_fragment, newFragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.addToBackStack(null);
ft.commit();
}
After each call to this function, a new entry is on the stack, and pressing back will pop it to return the user to whatever previous state the activity UI was in.
Source: https://developer.android.com/reference/android/app/Fragment.html
You need to get the same instance of FragOne from the viewpager.
First, you can only access the FragOne instance after the ViewPager is setup.
Then, try this:
fo = adapter.getItem(0)
Note:
Since you already have fragments, it would be better to let the fragment itself handle the UI-related actions (such as setting the textview) rather than from the Activity.
I have a main Activity in which I created a ViewPager that instantiate 3 other Fragments. One of these is a GridView which makes a popup appear when the user click on one item. Then, in this popup, I have a simple button.
What I want to do is: when the user click on this button, I would like to access a method in my main Activity (that should change the current item of my ViewPager) and then dismiss the popup.
I tried everything I could, but I cannot achieve this... I can set up the click event on my popup and dismiss it easily, but I didn't find out how I can access a method (or even a variable) from my popup to my main Activity.
I will put my most relevant code in here so you can understand the structure of my classes (hopefully...).
My main Activity:
public class MainActivity extends FirstActivity{
private ViewPager mViewPager;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mViewPager = (ViewPager) findViewById(R.id.viewpager);
// Set an Adapter on the ViewPager
mViewPager.setAdapter(new MainActivity_Adapter(getSupportFragmentManager()));
public void onSaveInstanceState(Bundle savedInstanceState)
{
menuBar.onSaveInstanceState(savedInstanceState);
}}
My ViewPager activity:
public class MainActivity_Adapter extends FragmentPagerAdapter{
public MainActivity_Adapter(FragmentManager fm)
{
super(fm);
}
#Override
public Fragment getItem(int position)
{
// Set the color background for each page
switch (position)
{
case 0:
return MainActivity_Inventory.newInstance();
case 1:
return MainActivity_Map.newInstance();
default:
return MainActivity_AR.newInstance();
}
}
// The number of Splash Screens to display
#Override
public int getCount()
{
return 3;
}}
My "Inventory" Fragment
public class MainActivity_Inventory extends Fragment implements View.OnClickListener{
public static MainActivity_Inventory newInstance()
{
MainActivity_Inventory frag = new MainActivity_Inventory();
Bundle b = new Bundle();
frag.setArguments(b);
return frag;
}
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// Select the layout
int layout;
layout = R.layout.activity_inventory_01;
// Inflate the layout resource file
View view = getActivity().getLayoutInflater().inflate(layout, container, false);
// Set the grid view
GridView gridview = (GridView) view.findViewById(R.id.inventory_gridView);
gridview.setAdapter(new InventoryImageAdapter(super.getActivity()));
gridview.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
public void onItemClick(AdapterView parent, View v, int position, long id)
{
// Create a popup to show item details
createPopup();
}
});
return view;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
super.onViewCreated(view, savedInstanceState);
}
public void createPopup()
{
DialogFragment newFragment = new PopupActivity_Inventory();
newFragment.show(getFragmentManager(), "itemDetails");
}
#Override
public void onClick(View v)
{
}}
And my popup dialog fragment:
public class PopupActivity_Inventory extends DialogFragment{
#Override
public Dialog onCreateDialog(final Bundle savedInstanceState)
{
// Build the alert dialog
final Dialog dialog = new Dialog(this.getActivity());
// Get the layout inflater
final LayoutInflater inflater = getActivity().getLayoutInflater();
// Set up the dialog box
dialog.setContentView(inflater.inflate(R.layout.activity_inventory_popup_01, null));
dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
dialog.getWindow().setGravity(Gravity.TOP);
//dialog.getWindow().getAttributes().y = 100;
(dialog.findViewById(R.id.brick_button_01)).setOnClickListener(new View.OnClickListener()
{
public void onClick(View view)
{
// When button is clicked, ACCESS MAIN ACTIVITY!
dialog.dismiss();
}
});
return dialog;
}}
I really hope you can help me with this... I really need to get it working. Thank you very much!
If you need further details or explanation, please just tell me.
The best thing to do is use EventBus library. I have a demo app in which you can add items to RecyclerView from anywhere within the app using EventBus. You can use it as a reference to simply do something else instead of current task. Here is the link to the repo:
https://github.com/code-crusher/android-demos/tree/master/EventBusDemo
And if you want to understand how it works you can refer to my article, it explains how to make communications like this easy:
https://medium.com/#code_crusher/eventbus-for-android
Hope it helps. Happy coding :)
Read this https://developer.android.com/training/basics/fragments/communicating.html
Look for "To allow a Fragment to communicate up to its Activity, you can define an interface in the Fragment class and implement it within the Activity...."
Another way to achieve it is using an EventBus and post events by Fragments to be caught by Activities.
I'm new to Android Studio, and have found similar questions but none with replies that apply to my current instance. I've been trying to work with RecyclerViews and have been getting the following error message whenever I open an activity with it. The code doesn't show any errors.
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.lucasrizzotto.edgeweather/com.lucasrizzotto.edgeweather.ui.HourlyForecastActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.support.v7.widget.RecyclerView.setLayoutManager(android.support.v7.widget.RecyclerView$LayoutManager)' on a null object reference
This is my adapter:
public class HourAdapter extends RecyclerView.Adapter<HourAdapter.HourViewHolder> {
private Hour[] mHours;
public HourAdapter(Hour[] hours) {
mHours = hours;
}
#Override
public HourViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).
inflate(R.layout.hourly_list_item, parent, false);
HourViewHolder viewHolder = new HourViewHolder(view);
return viewHolder;
}
#Override
public void onBindViewHolder(HourViewHolder holder, int position) {
holder.bindHour(mHours[position]);
}
#Override
public int getItemCount() {
return mHours.length;
}
public class HourViewHolder extends RecyclerView.ViewHolder {
public TextView mTimeLabel;
public TextView mTemperatureLabel;
public TextView mSummaryLabel;
public ImageView mIconImageView;
public HourViewHolder(View itemView) {
super(itemView);
mTimeLabel = (TextView) itemView.findViewById(R.id.timeLabel);
mTemperatureLabel = (TextView) itemView.findViewById(R.id.temperatureLabel);
mSummaryLabel = (TextView) itemView.findViewById(R.id.summaryLabel);
mIconImageView = (ImageView) itemView.findViewById(R.id.iconImageView);
}
public void bindHour(Hour hour) {
mTimeLabel.setText(hour.getHour());
mSummaryLabel.setText(hour.getSummary());
mTemperatureLabel.setText((int)hour.getTemperature());
mIconImageView.setImageResource(hour.getIconId());
}
}
}
And my Activity class (Keep in mind I'm using the ButterKnife plugin to avoid boilerplate code)
public class HourlyForecastActivity extends AppCompatActivity {
private Hour[] mHours;
#BindView(R.id.recyclerView) RecyclerView mRecyclerView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_hourly_forecast);
Intent intent = getIntent();
Parcelable[] parcelables = intent.getParcelableArrayExtra(MainActivity.HOURLY_FORECAST);
mHours = Arrays.copyOf(parcelables, parcelables.length, Hour[].class);
HourAdapter adapter = new HourAdapter(mHours);
mRecyclerView.setAdapter(adapter);
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);
mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.setHasFixedSize(true);
}
}
What am I overlooking?
Thanks in advance for the wisdom!
Try calling:
ButterKnife.bind(this);
after setting the content view
Since you are not doing this the conventional way, you need to actually call bind().
You are using ButterKnife to bind views which is good.
But it seems that you have forgotten to initialise ButterKnife for your current Activity.
Just do this,
ButterKnife.bind(this);
after this line,
setContentView(R.layout.activity_hourly_forecast);
This will initialise ButterKnife for your current Activity and you will be able to reference the RecyclerView from your layout correctly.
Hope it helps.
Basicly you need to initialized mRecyclerView
For example
mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
But in your case you use ButterKnife library for this.
So you need to call its bind() method after setContentView(R.layout.activity_hourly_forecast);
setContentView(R.layout.activity_hourly_forecast);
ButterKnife.bind(this);