Implementing callback in extendable button class - java

I have a very simple 'CustomButton' class which extends the default 'Button' class. My CustomButton uses onTouchEvent and I want to pass down a function from my Activity to the CustomButton and get it executed on touch down.
The CustomButton class is working fine, but I can't seem to figure out how to pass down a function to it.
Activity:
public class mainActivity extends Activity
{
#Override
public void onCreate( Bundle savedInstanceState )
{
super.onCreate( savedInstanceState );
Context context = getApplicationContext();
setContentView( R.layout.main );
LinearLayout root = (LinearLayout) findViewById( R.id.myLayout );
View child1 = getLayoutInflater().inflate( R.layout.child, null );
// Define the button
final CustomButtom myCustomButton = (CustomButtom)child1.findViewById( R.id.button_id );
myCustomButtom.setCallback( test ); // <-- I want to pass my 'test' function to CustomButton class,
// so it can get executed by the onTouchEvent
root.addView( myCustomButton );
super.onCreate( savedInstanceState );
}
private int test()
{
Log.d( "test", "Callback executed!" );
}
}
And this is my CustomButton class:
public class CustomButtom extends Button
{
private Function callback;
public CustomButtom(Context context, AttributeSet attrs) {
super(context, attrs);
this.setOnTouchListener
(
new OnTouchListener()
{
#Override
public boolean onTouch(View v, MotionEvent event)
{
if(event.getAction() == MotionEvent.ACTION_DOWN)
{
executeCallback(); // <-- My callback would get executed from here
}
return true;
}
}
);
}
public void setCallback(Function function)
{
callbackFunction = function; // Save the callback in a local variable
}
private boolean executeCallback()
{
return callbackFunction.execute(); // execute the callback
}
}
Is there a 'data type' such as 'Function' which I can use for this purpose or is there different way how to accomplish this? Thank you!

You usually need an object and a function when you want to perform an action in Java. This is what Interfaces are for. You declare an interface with the name of your function. Your Activity implements this interface and the function and then you pass to the button the instance of your activity. In your case you are probably looking for OnTouchListener or OnClickListener? If you want to have a special interface, you can declare it the same way.
public class mainActivity extends Activity implements OnClickListener
{
#Override
public void onCreate( Bundle savedInstanceState )
{
super.onCreate( savedInstanceState );
// I do not see any reason to use the Application Context here. Your Activity has the right context for your UI
// Context context = getApplicationContext();
setContentView( R.layout.main );
LinearLayout root = (LinearLayout) findViewById( R.id.myLayout );
View child1 = getLayoutInflater().inflate( R.layout.child, null );
// Define the button
final CustomButtom myCustomButton = (CustomButtom)child1.findViewById( R.id.button_id );
myCustomButtom.setOnClickListener(this);
root.addView( myCustomButton );
// you did this already
//super.onCreate( savedInstanceState );
}
public void onClick(View v)
{
Log.d( "test", "Callback executed!" );
}
}
EDIT:
to keep the logic inside the button you can create your own interface (OnClickListener is an interface) like this:
public interface OnCustomActionListener {
// you can remove the button as parameter if you do not care which button the action came from
void onCustomAction(CustomButton button);
}
public class CustomButtom extends Button {
OnCustomActionListener onCustomActionListener;
public void setOnCustomActionListener(OnCustomActionListener listener) {
this.onCustomActionListener = listener;
}
/* Creator like in your question mentioned */
private boolean executeCallback() {
if (this.onCustomActionListener != null) {
this.onCustomActionListener.onCustomAction(this);
}
}
}
in your Activity:
public class mainActivity extends Activity implements OnCustomActionListener {
...
myCustomButtom.setOnCustomActionListener(this);
...
public void onCustomAction(CustomButton button) {
// do something
}

Related

How can you change ViewPager2 position inside the ViewPager2Adapter?

I programmed a Vocabulary Trainer with Vocabulary Cards. The Vocabulary Cards are Entries in a Room Database created from an asset. I am displaying these Vocabulary Cards with ViewPager2 in an Activity. I have a 'correct' and a 'false' button and when the user clicks on either, I want to update the Vocabulary Card (-> The entry in the sqlite database) and automatically swipe to the next item of the ViewPager2.
If I implement the buttons in the ViewPager2Adapter, I can't find a way to change the position of the ViewPager2. If I implement the buttons in the activity the sqlite entry does not update properly (After it updates the entry, the activity is constantly refreshed, it seems like it never the leaves the OnClick methode of the button).
So is it possible to change the position of ViewPager2 from inside the ViewPager2Adpater?
Thanks for your help!
That is the relevant code if I have the buttons in my ViewPager2Adapter. Here I don't know how to change the position of the ViewPager2
public void onBindViewHolder(#NonNull #NotNull ViewHolder holder, int position) {
VocabularyCard vocabularyCard = currentCards.get(position);
holder.btn_correct.setOnClickListener(view -> {
vocabularyViewModel.updateSingleVocabularyCard(vocabularyCard);
});
holder.btn_false.setOnClickListener(v15 -> {
vocabularyViewModel.updateSingleVocabularyCard(vocabularyCard);
});
That is the relevant code if I have the buttons in the Activity. Here the update function triggers an infinite updating of the Activity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
initAll();
btn_correct_2.setOnClickListener(view -> {
int currentPos = viewpager2.getCurrentItem();
vocabularyViewModel.getCurrentCards().observe(this, vocabularyCards -> {
if (vocabularyCards.size() == currentPos){
Intent intent = new Intent(TestActivity.this, MainActivity.class);
startActivity(intent);
}else {
viewpager2.setCurrentItem(currentPos + 1);
}
VocabularyCard vocabularyCard = vocabularyCards.get(currentPos);
vocabularyViewModel.updateSingleVocabularyCard(vocabularyCard);
});
});
btn_false_2.setOnClickListener(view -> {
int currentPos = viewpager2.getCurrentItem();
vocabularyViewModel.getCurrentCards().observe(this, vocabularyCards -> {
if (vocabularyCards.size() == currentPos){
Intent intent = new Intent(TestActivity.this, MainActivity.class);
startActivity(intent);
}else {
viewpager2.setCurrentItem(currentPos + 1);
}
VocabularyCard vocabularyCard = vocabularyCards.get(currentPos);
vocabularyViewModel.updateSingleVocabularyCard(vocabularyCard);
});
});
Objects.requireNonNull(getSupportActionBar()).setTitle(getResources().getString(R.string.learn_new_words));
LiveData<List<VocabularyCard>> allNewCards = vocabularyViewModel.getAllNewCards(goal);
allNewCards.observe(this, vocabularyCards -> vocabularyViewModel.setCurrentCards(vocabularyCards));
vocabularyViewModel.getCurrentCards().observe(this, vocabularyCards -> {
viewPager2Adapter.setCurrentCards(vocabularyCards);
viewpager2.setAdapter(viewPager2Adapter);
viewpager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels);
}
#Override
public void onPageSelected(int position) {
super.onPageSelected(position);
}
#Override
public void onPageScrollStateChanged(int state) {
super.onPageScrollStateChanged(state);
}
});
});
The update function in the Room DAO is straightforward:
#Update
void updateSingleVocabularyCard(VocabularyCard vocabularyCard);
I left out all the code that is not relevant.
There are several ways to propagate an event from the adapter to the activity where you manage your cards using ViewPager2. Let's have a look how it can be done either using an interface or using the same view model. But in any case I strongly recommend you to update your database in a background thread to prevent any possible UI lags.
1. Using an interface
This option is more flexible since you can propagate events as well as pass data as parameters. You can also reuse this interface for other cases. As far as I See you have a holder that has 2 buttons for the users to make choices. So our event here would be something like ChoiceEventListener, let's call this interface like so. Then you'd have to add a method to handle this event from within anywhere you wanna hear this event, and let's call its handle method onChoice(). Finally we would need a variable to indicate what the choice is. Now that ready to implement, let's write the new interface...
ChoiceEventListener.java
public interface ChoiceEventListener {
void onChoice(VocabularyCard vocabularyCard, boolean choice);
}
The next thing to do is to implement this interface where you want to listen to this event. In this case it is in your activity. There are 2 ways to do this:
You make your activity to inherit its methods using the implements keyword
YourActivity.java
public class YourActivity extends AppCompatActivity implements ChoiceEventListener {
// Use a background thread for database operations
private Executor executor = Executors.newSingleThreadExecutor();
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
initAll();
// You must construct your adapter class with the listener
ViewPager2Adapter adapter = new ViewPager2Adapter(/* Other params... */, this);
}
#Override
public void onChoice(VocabularyCard vocabularyCard, boolean choice) {
if(choice) {
// User pressed the correct button
}
else {
// User pressed the false button
}
// Update card in the background
executor.execute(()-> vocabularyViewModel.updateSingleVocabularyCard(vocabularyCard));
}
}
You can implement it as an anonymous function
YourActivity.java
public class YourActivity extends AppCompatActivity {
// Use a background thread for database operations
private Executor executor = Executors.newSingleThreadExecutor();
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
initAll();
// You must construct your adapter class with the listener
ViewPager2Adapter adapter = new ViewPager2Adapter(/* Other params... */, (vocabularyCard, choice) -> {
if(choice) {
// User pressed the correct button
}
else {
// User pressed the false button
}
// Update card in the background
executor.execute(()-> vocabularyViewModel.updateSingleVocabularyCard(vocabularyCard));
});
}
}
Finally the ViewPager2Adapter class implementation would be something like this:
ViewPager2Adapter.java
public class ViewPager2Adapter extends RecyclerView.Adapter<ViewPager2ViewHolder> {
// Here is your listener to deliver the choice event to it
private final ChoiceEventListener listener;
// Constructor
public ViewPager2Adapter(/* Other params... */, ChoiceEventListener listener) {
/* Other inits */
this.listener = listener;
}
public void onBindViewHolder(#NonNull #NotNull ViewHolder holder, int position) {
VocabularyCard vocabularyCard = currentCards.get(position);
holder.btn_correct.setOnClickListener(view -> {
listener.onChoice(vocabularyCard, true); // true for correct
});
holder.btn_false.setOnClickListener(v15 -> {
listener.onChoice(vocabularyCard, false); // false for false :)
});
}
}
2. Use the ViewModel for inter-communication
In this option we use a LiveData object to make page switching. The only thing you need to know in your activity is the current position which you get it from the adapter class. Once you update it in the adapter, set the current position value in live data so that you can switch the page in your activity.
VocabularyViewModel.java
public class VocabularyViewModel extends ViewModel {
public MutableLiveData<Integer> mldCurrentPosition = new MutableLiveData<>(0);
}
YourActivity.java
public class YourActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
initAll();
vocabularyViewModel.mldCurrentPosition().observe(this, currentPosition -> {
if(currenPosition == null) return; // ignore when null
viewpager2.setCurrentItem(currentPosition + 1);
}
}
}
Finally the ViewPager2Adapter class implementation would be something like this:
ViewPager2Adapter.java
public class ViewPager2Adapter extends RecyclerView.Adapter<ViewPager2ViewHolder> {
// Use a background thread for database operations
private Executor executor = Executors.newSingleThreadExecutor();
public void onBindViewHolder(#NonNull #NotNull ViewHolder holder, int position) {
VocabularyCard vocabularyCard = currentCards.get(position);
holder.btn_correct.setOnClickListener(view -> {
// Update card in the background
executor.execute(()-> vocabularyViewModel.updateSingleVocabularyCard(vocabularyCard));
// Then invoke switching to the next card
vocabularyViewModel.mldCurrentPosition.setValue(position + 1);
});
holder.btn_false.setOnClickListener(v15 -> {
// Update card in the background
executor.execute(()-> vocabularyViewModel.updateSingleVocabularyCard(vocabularyCard));
// Then invoke switching to the next card
vocabularyViewModel.mldCurrentPosition.setValue(position + 1);
});
}
}

How do I call one method of an activity from another class

This is a part of my activity class,
public class StatusActivity extends AppCompatActivity {
private boolean cFlag = false;
public boolean getFlag() { return cFlag; }
public void setFlag(boolean cFlag) {
this.cFlag = cFlag;
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.list);
adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1,
android.R.id.text1, messages);
ListView listView = findViewById(android.R.id.list);
listView.setAdapter(adapter);
adapters.add(adapter);
Button btn = findViewById(R.id.btnCustomerCheckIn);
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
setFlag(true);
cFlag = getFlag();
Intent intent = new Intent(StatusActivity.this, MainActivity.class);
Toast.makeText(StatusActivity.this, "customer checked in",
Toast.LENGTH_LONG).show();
startActivity(intent);
}
});
}
this is a part of another class named as position
public class Position {
StatusActivity statusactivity = new StatusActivity();
public boolean ccflag = statusactivity.getFlag();
statusactivity.setFlag(false);
}
when i am calling
statusactivity.setFlag(false);
it is showing an error. couldn't recognize that what is the error that i am getting. but
statusactivity.getFlag();
is working properly. any help is appreciated
StatusActivity statusactivity = new StatusActivity();
This is totally wrong because you are trying to create a new Instance of activity.
If you want to use "setFlag" method from other activity then you must create a static method inside StatusActivity so you can access using directly StatusActivity.
And If you want to call from any fragment of this activity then please get an instance of this activity by the cast from "getActivity()" to StatusActivity and use that instance for call "setFlag" or "getFlag" method.
You can implement like below in Activity.
private static boolean cFlag = false;
public static boolean getFlag() {
return cFlag;
}
public static void setFlag(boolean cFlag) {
StatusActivity.cFlag = cFlag;
}
and call from position class like below
public class Position {
public boolean ccflag = StatusActivity.getFlag();
StatusActivity.setFlag(false);
}
you can not instantiate Activity class. if you want to call a method from activity, fist you should check that activity already running and not destroyed then by having the context of your class you just cast it like below then use its method
StatusActivity statusactivity= (StatusActivity )context;
statusactivity.setFlag(false);

Android Capturing touches on LinearLayout: Cannot Resolve Symbol setOnTouchListener.

I have a (seemingly) simple problem: I'm trying to set an onTouchListener on a child linear layout but I can't get my code to compile. I get the error "Cannot Resolve Symbol setOnTouchListener" when I try to use setOnTouchListener() on my chosen view.
How can I record touches on my LinearLayout? What am I doing wrong?
MainActivity.java
public class MainActivity extends FragmentActivity {
public static LinearLayout glView;
public static OpenGL_GLSurface foo;
public TouchController touchSurface;
void configView(){ // used to configure an opengl view
foo = new OpenGL_GLSurface(this);
setContentView(R.layout.activity_main);
glView = (LinearLayout)findViewById(R.id.openglsurface);
RelativeLayout.LayoutParams glParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.MATCH_PARENT);
glView.addView(foo, glParams);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
...
touchSurface = new TouchController(this); //initialize touchable surface
}}
TouchController.java
public class TouchController {
private Context mContext;
public TouchController(Context c) { //constructor
mContext = c;
}
View.OnTouchListener touch = new View.OnTouchListener() { //set OnTouchListener to OpenGL View
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (maskedAction) {
//do stuff
}
return true;
}
};
MainActivity.glView.setOnTouchListener(touch); //Compilation Error here # setOnTouchListener
}
The issue is in your TouchController, when you are setting the touch listener, this line:
MainActivity.glView.setOnTouchListener(touch);
That line of code is invalid java code because is just hanging around in the class. It must be inside a method like the constructor. Like this:
Edit:
public class TouchController {
private Context mContext;
public TouchController(Context c) { //constructor
mContext = c;
View.OnTouchListener touch = new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
switch (maskedAction) {
//do stuff
}
return true;
}
};
//Register touch listener here in constructor or in another method
CourseListActivity.glView.setOnTouchListener(touch);
}
}
You should consider moving the assignment of your member variable "touch" inside the constructor too, just before you set the touch listener. It will be more organized.

GWT Button onClick() return a value

I need to create 2 generic button called yes and no with 2 return 0 if no 1 if yes. I see the onclick method is void and not return int, how can i do?
YesButton.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
??? result ??
}
});
That's not the way to do it. Not knowing your specific requirement makes it a little hard, but I'll venture a suggestion. First define a controller/mediator/whachamacallit with the operations that the view can perform:
public interface MyListener
{
void onYesClick();
void onNoClick();
}
(Could be a concrete class also, but yes and no clicking seems very generic, so we could reuse that elsewhere)
In your view class you would then have
public class MyView
{
private MyListener listener;
private Button yesButton = new Button( "yessir!" );
private Button noButton = new Button( "no way!" );
public MyView( MyListener listener ) { this.listener = listener; }
yesButton.addClickHandler( new ClickHandler()
{
#Override
public void onClick( ClickEvent event )
{
listener.onYesClick(); // similarly .onNoClick() for the "No" button
}
} );
// etc
...
}
Hope that helps you a bit further.

Java, using an interface as a callback

I have been developing a simple touch handler for Android with the possibilites of firing callbacks like onUpdate (when the screen is touched) without having to setup threads. My problem is that my knowledge of Java is fairly limited and i can't do it because i know very little of how to use interfaces. I'm pretty sure that my problem may be a simple typo or something, but i get a NullPointerException when i execute the method from the touch handler (which processed the touch information) so that i can do what i need in the main activity class.
This is the main class code (cut from the irrelevant stuff):
//package and imports
public class Test extends Activity implements TouchHelper {
StringBuilder builder = new StringBuilder();
TextView textView;
TouchReader touchReader;
List<TouchTable> touchTablesArray;
TouchTable touchTable;
public static final String Tag = "TouchTest";
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
textView = new TextView(this);
Log.d(Tag, "TextView initialized " + textView);
textView.setText("Touch and drag (multiple fingers supported)!");
touchReader = new TouchReader(textView);
Log.d(Tag, "touchReader initialized");
touchTablesArray = touchReader.getTouchTables();
setContentView(textView);
}
#Override
public void onTouchUpdate(int pointerId)
{
Log.d(Tag, "onTouchUpdate called");
touchTable = touchTablesArray.get(pointerId);
Log.d(Tag, "touchTable get successful");
//writing on stringbuilder
}
}
This is the code of the handler itself:
//package and imports
public class TouchReader implements OnTouchListener
{
public final static String Tag = "TouchReader";
List<TouchTable> touchTables;
TouchHelper helper;
TouchTable touchTable = new TouchTable();
public TouchReader(View view)
{
view.setOnTouchListener(this);
touchTables = new ArrayList<TouchTable>(10);
Log.d(Tag, "TouchReader initialized");
}
public boolean onTouch(View v, MotionEvent event)
{
synchronized(this)
{
//all the common code handling the actual handling, with switches and such
touchTables.add(pointerId, touchTable); //obviously the pointerId is defined earlier
Log.d(Tag, "Values updated");
helper.onTouchUpdate(pointerId); //the exception is here
Log.d(Tag, "Update called");
}
return true;
}
public List<TouchTable> getTouchTables()
{
synchronized(this)
{
return touchTables;
}
}
}
As you can see the error is most likely due to my inability to correctly use an interface, and yet all the official docs confused me even more.
Finally, the tiny code of the interface:
//package
public interface TouchHelper
{
public void onTouchUpdate(int pointerId);
}
I hope this question isn't too noobish to post it here :)
EDIT: Thanks to all for the help, in the end i followed Bughi's solution.
Your TouchHelper helper; is null, it needs a instance of the interface to be able to call methods on it -in your case the main activity class that implements your interface-
Make a set method for the listener
public void setOnTouchListener(TouchHelper helper)
{
this.helper = helper;
}
Then call it from on create:
public class Test extends Activity implements TouchHelper {
...
#Override
public void onCreate(Bundle savedInstanceState)
{
...
touchReader = new TouchReader(textView);
touchReader.setOnTouchListener(this);
...
}
}
Also add a null check to your on touch method:
public boolean onTouch(View v, MotionEvent event)
{
synchronized(this)
{
//all the common code handling the actual handling, with switches and such
touchTables.add(pointerId, touchTable); //obviously the pointerId is defined earlier
Log.d(Tag, "Values updated");
if (helper != null)
helper.onTouchUpdate(pointerId); //the exception is here
Log.d(Tag, "Update called");
}
return true;
}
If the NullPointerException is here:
helper.onTouchUpdate(pointerId);
Then simply helper is null, where do you initialize it?
I see that you define it:
TouchHelper helper;
But do you ever have?
helper = ...
I know this is old, but I was stuck on this myself. Sam's post above helped me think of it.
I finally added an onAttach method that that checks that the interface is initialized as well as implemented to the main activity that it interfaces with. I added a Log.i inside the main activity to test.
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mainActivityCallback = (OnSomethingSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnSomethingSelectedListener");
}
}
In TouchReader you define a TouchHelper but nowhere in the code an object is created or an existing object is assigned to that attribute. So it is still null when you try to use it.
helper is null in your in TouchReader
To fix this make the TouchReader take a TouchHelper:
public TouchReader(View view, TouchHelper helper) {
...
this.helper = helper;
...
}
Then in your activity:
touchReader = new TouchReader(textView, this);
Try initializing it in your constructor; all reference that aren't initialized are set to null.
// I see no reason why this should be a member variable; make it local
StringBuilder builder = new StringBuilder();
TextView textView;
TouchReader touchReader;
List<TouchTable> touchTablesArray;
TouchTable touchTable;
public TouchReader(View view)
{
// textView is null
// touchReader is null
view.setOnTouchListener(this);
// why "10"? why a List of touchTables and a touchTable member variable? why both?
touchTables = new ArrayList<TouchTable>(10);
Log.d(Tag, "TouchReader initialized");
// touchTable is null;
}

Categories

Resources