Imitate 'onActivityResult' inside an Adapter of RecyclerView - java

I have a RecyclerView which items' contain a button that launches an activity using startActivityForResult. The onClick method was implemented inside the adapter, and onActivityResult from the fragment that contains the RecyclerView was not called upon returning from the new activity. As I learned, this is to be expected, because startActivityForResult was not called from this fragment.
The closest thing to a solution I found was this:
onActivityResult inside a RecyclerView.Adapter not being used
But when trying to use an interface to implement the onClick method, I now face a new problem: I can't call the onClick method inside the adapter, although it's implemented inside the activity, because the Adapter's context is static, and the method is non-static.
Below are some code portions to explain better the current situations. I'm in desperate need for an alternative solution, I really searched through and through. Thanks.
The fragment implements the interface:
public class EditableOffersListFragment extends Fragment implements OnClickButtonListener { ...
#Override
public void onClickButton(View v, int position, ArrayList<Offer> offers) {
Offer from_item = offers.get(position);
Intent intent = new Intent(getActivity(), OfferDetailsPopupActivity.class);
intent.putExtra("new", false);
intent.putExtra("offer_fromRecycler", from_item);
getActivity().startActivityForResult(intent, HTZ_ADD_OFFER);
}
...}
The interface (in a seperate file):
public interface OnClickButtonListener {
void onClickButton(View v, int position, ArrayList<Offer> offers);
}
And inside the adapter:
mEditOfferButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
OnClickButtonListener.onClickButton(v, getAdapterPosition(), mOffers);
}
});

I think the problem you're currently facing is that you're calling startActivityForResult from the Activity containing the current fragment rather than the Fragment itself. So using this or just plainly calling the startActivityForResult will call it from the Fragment instead of the Activity and this fragment will be callbacked with the result from the other Activity.
public class EditableOffersListFragment extends Fragment implements OnClickButtonListener { ...
#Override
public void onClickButton(View v, int position, ArrayList<Offer> offers) {
Offer from_item = offers.get(position);
Intent intent = new Intent(getActivity(), OfferDetailsPopupActivity.class);
intent.putExtra("new", false);
intent.putExtra("offer_fromRecycler", from_item);
startActivityForResult(intent, HTZ_ADD_OFFER); // <--- Note that this line has changed
}
#Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
// Do your result handling here
}
}

Ok, finally solved it.
What worked was adding a OnClickButtonListener member to the Adapter, which is passed from the Fragment in the Adapter's constructor (the Fragment is implementing OnClickButtonListener so I just passed 'this' to the constructor).
That way, all of the actions performed in the adapter are on an instance of OnClickButtonListener and the static/non-static problem was solved.

Related

Implementing a class that will override the onClick()

I want to create a method that, when implemented in other classes, you just need to pass some parameters and then call the - onclick() function to set the element.
At the moment, I´ve just done this. But this gives me a RunTimeException
Unable to start activity ComponentInfo{com.example.test/com.example.testActivity.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'android.app.ActivityThread$ApplicationThread android.app.ActivityThread.getApplicationThread()' on a null object reference
my code:
Class: clickeable
imports ...
public class ClickeableOptions implements View.OnClickListener{
private CardView cardView;
private Context cont;
private Class actTarget;
public OpcionesMainClickeables() {}
public ClickeableOptions(CardView cardView, Context cont, Class actTarget) {
this.cardView = cardView;
this.cont = cont;
this.actTarget= actTarget;
}
//Getters and Setters
#Override
public void onClick(View v) {
getCardView().setOnClickListener(this);
Intent intent = new Intent(this.getCont(), this.getActTarget());
startActivity(intent);
}
}
And i want to implement this class like this...
public class MainActivity extends AppCompatActivity{
private CardView cvRegistration;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
cvRegistration = (CardView) findViewById(R.id.cvRegistration);
ClickeableOptions optionRegistration = new ClickeableOptions(
cvRegistration, this, Registration.class
);
optionRegistration.onClick(optionRegistration.getCardView());
}
I've implemented the method in the same file, but I want to do it this way to keep things more tidy.
I think the problem is in the use of this, but i really don't get it
No need to call onClick explicitly from your Activity. Just try like below:
public ClickeableOptions(CardView cardView, Context cont, Class actTarget) {
this.cardView = cardView;
this.cont = cont;
this.actTarget= actTarget;
this.cardView.setOnClickListener(this);
}
And inside onClick
#Override
public void onClick(View v) {
Intent intent = new Intent(getCont(), getActTarget());
getCont().startActivity(intent);
}
And remove this line from Activity.
//optionRegistration.onClick(optionRegistration.getCardView());
Now when you clicked your CardView, Then Activity transitions start.
Try to replace this line:
startActivity(intent);
with
getCont().startActivity(intent);
Also, why are you inheriting ClickeableOptions from AppCompatActivity?
Although you have instantiated the ClickeableOptions (derived from Activity) object, none of its Activity life cycle methods have been called and none of the super class instantiation related work (normally should be done in onCreate) has been accomplished. Hence the ActivityThread is simply null and you've got an exception when invoking
startActivity(intent);
If you want to start another Activity on your CardView click then you need to
follow Leo Leontev's advice:
call
getCont().startActivity(intent)
instead of
startActivity(intent)
move the line
getCardView().setOnClickListener(this)
to the ClickeableOptions custom constructor's most bottom line
delete this line from your MainActivity$onCreate() method:
optionRegistration.onClick(optionRegistration.getCardView());
After all of above is done you'll be able to start new activity on click event.

How Can I use Same Adapter of a GridView on Different activities

Let say I have two Activities, Activity A and Activity B.
Activity A displays a list of images using the Adapter Z.
When user clicks on any image in Activity A, they will be taken to Activity B to show the full image. I'm passing image path and grid position to Activity using Intent.
Now in Activity B, I place a delete button which should delete the imagepath from the gridview adapter.
Problem is:
How can I access the Activity A adapter in activity B to call remove(position) method in my adapter.
So I can call notifyDataSetChanged in onResume of Activity A to update the gridview images.
Activity A
MyGridView = (GridView) findViewById(R.id.gridview);
adapter = new MyAdapter(this);
MyGridView .setAdapter(adapter );
Intent fullImageActivity = new Intent(getApplicationContext(), ActivityB.class);
fullImageActivity.putExtra("position", position);
fullImageActivity.putExtra("path", mediaPath);
startActivity(fullImageActivity);
Activity B
Intent i = getIntent();
// I'm getting position and path from setOnItemClickListener
position = i.getExtras().getInt("position");
path = i.getExtras().getString("path");
// I want to remove path from my adapter after clicking delete button in Activity B
Adapter
public ArrayList<String> images;
public void remove(int position){
images.remove(position);
}
use startActivityForResult.
Intent fullImageActivity = new Intent(getApplicationContext(), ActivityB.class);
fullImageActivity.putExtra("position", position);
fullImageActivity.putExtra("path", mediaPath);
startActivityForResult(fullImageActivity, 2);
check if deleted at particular position in Activity B . here I override onBackPressed() (For Ex.) method
public void onBackPressed(){
super.onBackPressed()
Intent intent=new Intent();
intent.putExtra("isdeleted",true);
intent.putExtra("pos",position);
setResult(2,intent);
finish();
}
Handle it in onActivityResult in Activity A.
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
// check if the request code is same as what is passed here it is 2
if(requestCode==2)
{
if(data.getBooleanExtra("isdeleted")){
remove from position array and notify dataset change. // pos = data.getIntExtra("pos")
}
}
}
Sorry for TYPO.
The key here is that you need to share the data and not really the adapter.
Using a singleton
You can use a singleton class to hold the image data and then access the same in both the activities. This ensures sync between both activities at all times.
Singleton Class
public class ImageData{
private ArrayList<ImageModel> mDataOfImages;
private ImageData mHelperInstance;
private ImageData(){
//private constructor to ensure singleton
}
public static ImageData getInstance(){
// return an ImageData instance according to your implementation
// of singleton pattern
}
private void setData(ArrayList<ImageModel> newData){
this.mDataOfImages = newData;
}
private void removeImage(int position){
if(this.mDataOfImages !=null && this.mDataOfImages.size() > position){
mDataOfImages.remove(position);
}
}
}
Activity A
private void saveImageData(ArrayList<ImageModel> data){
if(data !=null){
if(mAdapter !=null){
mAdapter.setData(data);
}
}
}
//Call notifydatasetchanged when activity is opened again
#Override
protected void onStart() {
if(mAdapter !=null){
mAdapter.notifyDataSetChanged();
}
}
MyAdapter
public void setData(ArrayList<ImageMode> newData){
if(newData !=null){
ImageDataSingleton.getInstance().setData(newData);
notifyDataSetChanged();
}
}
Activity B
Use the singleton class to display images in Activity B. Because you are using the model array list, you can easily implement right/left swipes and delete multiple images.
//Delete a image
private void deleteImage(){
ImageDataSingleton.getInstance().removeImage(getCurrentPosition());
// Rest of deletion handling like moving to right or left image
}
I think that you should build your remove method in activity A and be sure that it is static:
public static void remove(int position){
images.remove(position);
}
now you can call it from activity B like this:
ActivityA.remove(position);
I think this will work.

Fragment communicating with an Activity

I am confused about how communication with a Fragment and an Activity is made. For example, an interface was defined here (https://developer.android.com/training/basics/fragments/communicating.html).
public class HeadlinesFragment extends ListFragment {
OnHeadlineSelectedListener mCallback;
// Container Activity must implement this interface
public interface OnHeadlineSelectedListener {
public void onArticleSelected(int position);
}
...
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
// The user clicked on a list item.
mCallback.onArticleSelected(position);
}
}
Eventually, the following method is called from the MainActivity.
public static class MainActivity extends Activity
implements HeadlinesFragment.OnHeadlineSelectedListener{
...
public void onArticleSelected(int position) {
// Do something
}
}
My questions are:
How does mCallback "know" which onArticleSelected method to call (as there might be other classes that have implemented OnHeadlineSelectedListener).
mCallback.onArticleSelected(position);
I wouldn't be confused if it went:
mCallback = new OnHeadSelectedListener() {
#Override
public void onArticleSelected(int position)
// Do something
}
and then mCallback is referred in some way in MainActivity to utilize the onArticleSelected method in some way. In the example code, however, the line intelligently sticks to "an" interface. How does that happen?
Also, I found that the Log I implemented onArticleSelected method from MainActivity is called previously to the one in onListItemClick method in HeadlineFragment. Is it expected?
Since you defined the OnHeadlineSelectedListener interface with only one method, and your activity implements it, there's no ambiguity in choosing the method when you use the activity as instance of this interface, 'cause all you know about activity while using it as instance of OnHeadlineSelectedListener interface is a presence of onArticleSelected(int) method in it.
It depends on when you call the logging function - before or after calling the callback method.
P.S. While this kind of communication between Activity and Fragment (or any other objects) is perfectly fine, personally I prefer the Event Bus approach, 'cause it gives us a possibility to organize code in a low coupled manner. Here are some nice implementations of Event Bus pattern:
https://github.com/greenrobot/EventBus
http://square.github.io/otto/
Take a look at them if you are interested in this approach.
Your mCallback is your activity, in the onAttach method of your fragment, you will set the activity as listener for your fragment. By this way, this is normal that the activity is notified when you call mCallback.onArticleSelected(position);
For your first part of question
You should have a look on onAttach and onDetach methods-
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mCallbacks = (OnHeadlineSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException("Activity must implement OnHeadlineSelectedListener.");
}
}
#Override
public void onDetach() {
super.onDetach();
mCallbacks = null;
}
Second part-
The behavior is not expected.
You should not make your activity class static as well.

Fragment transactions: how to exchange data to former fragment

Okay, so I have a MainActivity.java which has a Fragment, called FavoritesFragment.java, at some point I call a MyListFragment.java where people are supposed to choose a list element.
After that I want to get their choice back in my FavoritesFragment.java.
So I implemented an interface for that. In MyListFragment, a method is called, that sets settings in FavoritesFragment:
From MyListFragment:
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
Station s = (Station) l.getItemAtPosition(position);
updater.stationChosen(s, isFrom);
getFragmentManager().popBackStack();
}
Where updater is my interface.
So, everything works fine, my Content in FavoritesFragment is updated and everything. But after I call popBackStack() everything in FavoritesFragmentreset to former glory.
When I used to do everything with Activities there was this onActivityResult(...) callback from which you could exchange data. How can I do this with fragments? I don't want to use Activities for this minor case, and I want to have the list in it's own class, because I'm going to need it in other classes.
You need some place to keep track of favorites that is outside of the Fragments. One place is in the Activity that hosts the Fragments, which you can access via getActivity() and casting it to MainActivity or some interface that you define.
For example:
public interface FavoriteManager {
void onFavoriteAdded(Station s);
void onFavoriteRemoved(Station s);
List<Station> getFavorites();
etc.
}
public class MainActivity extends FragmentActivity implements FavoriteManager {
...
private List<Station> mFavorites;
...
#Override
public void onFavoriteAdded(Station s) {
mFavorites.add(s);
}
...
#Override
public List<Station> getFavorites() {
return mFavorites;
}
}
public class FavoritesFragment extends Fragment {
...
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
Station s = (Station) l.getItemAtPosition(position);
FavoriteManager manager = (FavoriteManager) getActivity();
manager.onFavoriteAdded(s);
getFragmentManager().popBackStack();
}
...
}
public class MyListFragment extends Fragment {
...
#Override
public void onStart() {
List<Station> favorites = ((FavoriteManager) getActivity()).getFavorites();
populateUi(favorites);
}
...
}
Another option is to use a database and a ContentProvider, as shown here: http://developer.android.com/guide/topics/providers/content-providers.html
Perhaps it's better to think about the fragments existing simultaneously than one after the other (like activities would). You aren't calling a Fragment and getting a result, but calling the fragment and allowing it to change data.
I would think the easiest thing to use the Observer pattern to have your FavoritesFragment observe changes on the underlying data, and make sure that the update from MyListFragment calls notifyobservers(). Then when FavoritesFragment comes back to the front, it'll notice the change

How to Correctly Start Activity from custom View (Java Class)?

I'm trying to define a custom Java class (extending a LinearLayout), which needs to start an activity on click. My code looks like this :
public ArizaSatiri(Context context/*, AttributeSet attrs , final Activity aktivite*/ , JSONObject mysql_satiri)
{
super(context/*, attrs*/);
// code to initialize my view :
final Context finalContext = context;
this.setOnClickListener(new OnClickListener() {#Override
public void onClick(View v) {
Intent newIntent = new Intent(finalContext, ArizaDetaylari.class);
finalContext.startActivity(newIntent);
}//onClick
});
}
But when I clicked on the instantiated view, I get the error :
Calling startActivity() from outside of an Activity context requires the
FLAG_ACTIVITY_NEW_TASK flag. Is this really what you want?
As you can see from the code, I tried passing the Activity to the constructor, and that worked. But is that the correct way? Which way would you reccomend ?
Edit:
And I also need to call setTypeFace() at some point. Should I use context, or Activity for that ?
Try this:
this.setOnClickListener(new OnClickListener() {#Override
public void onClick(View v) {
Intent newIntent = new Intent(finalContext, ArizaDetaylari.class);
newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
finalContext.startActivity(newIntent);
}//onClick
});
You can put your code into onAttachedToWindow() of your custom view class.
#Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
final Context context = ArizaSatiri.this.getContext();
Intent intent = new Intent(context , ArizaDetaylari.class);
context.startActivity(intent);
}
});
}
This helps because this function is called after your view is added to the activity, the view has the reference of it. Constructor of views probably run before being added to the activity, thus the error.
This should also work if you declare your view in xml instead of creating it programmatically. (Not tested yet)
If take a look to the Context class reference you could see that Activity is an indirect subclass. So if you use an Activity as Context your code should work.
For example:
public ArizaSatiri(Context context, JSONObject mysql_satiri){ ...}
you can change the call to the ArizaSatiri constructor:
new ArizaSatiri(myActivity, mysql_satiri);
Hope it helps.
If context (finalContext) that you are using is referring to ApplicationContext then you need to use the flag. Just add the sentence newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); before finalContext.startActivity(newIntent);
Problem with this approach especially when you are starting something outside of your app for ex: mail client etc, is it will continue to be there in the recent apps stack even after the activity is completed, in case of mail client it continues to be there even after mail has been sent

Categories

Resources