In the MainActivity java, how to call the anko class directly? - java

I used the anko library to create a login view.
class SingInView : AnkoComponent<SingleInActivity> {
override fun createView(ui: AnkoContext<SingleInActivity>) = with(ui) {
verticalLayout {
lparams(width = matchParent, height = matchParent)
textView("Member Login")
editText {
hint = "E-mail"
}
editText {
hint = "PassWord"
}
button("Login")
}
}
}
and SingleInActivity.kt
class SingleInActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState)
SingInView().setContentView(this)
and MainActivity.java
public class MainActivity extends AppCompatActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
startActivity(new Intent(this, SingInView.class));
finish();
}
}
current My app MainActivity -> SingleInActivity -> SingInView .
of course it can be made simply.
but there is a condition
1. MainActivity is java (kotlin prohibition)
2. use only MainActivity, SingInView.
How to solve this problem?
How to call the Anko class directly from a Java class

If you dig through the Anko source code you'll quickly find this:
interface AnkoComponent<in T> {
fun createView(ui: AnkoContext<T>): View
}
And from the wiki (where MyActivityUI is the component): MyActivityUI().setContentView(this). Now, the AnkoComponent is just an interface and the setContentView method is an extension function that returns createView.
Anyways, the setContentView extension function passes the last variable of the AnkoContextImpl as true. The last variable is whether or not to actually set the content view, which is the reason the activity is passed in the first place.
TL;DR (and possibly more sensible summary of my point):
The component is not an Activity
The setContentView method is not a replacement for setContentView in an Activity; just a wrapper for it.
And since it isn't an activity, you can't use an intent into it. And, as a result of that, you cannot use it standalone. You need an activity. Now, you can of course use the regular approach, but there's also another way. Since the AnkoComponent itself doesn't have any fields, it can be serialized without much trouble. Just to clarify: some fields can be serialized even if it isn't serializable (all though some classes like Context cannot be serialized). Anyways, you create an activity:
class AnkoComponentActivity : AppCompatActivity(){//Can be a regular Activity too
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState);
val component = intent.getSerializableExtra("uiComponent") as AnkoComponent<AnkoComponentActivity>//The type has to match this activity, or setContentView won't allow passing `this`
component.setContentView(this)//The context of the activity doesn't get passed until this point, which enables the use of this system.
}
}
Or it's equivalent in Java:
public class AnkoComponentActivity extends AppCompatActivity {
public void onCreate(Bundle sis){
super.onCreate(sis);
AnkoComponent<AnkoComponentActivity> component = (AnkoComponent<AnkoComponentActivity>) getIntent().getSerializableExtra("uiComponent");
org.jetbrains.anko.AnkoContextKt.setContentView(component, this);//For reference, this is how you call Kotlin extension functions from Java
}
}
Note that any UI component sent to this class has to be declared with <AnkoComponentActivity>. In addition, the components have to implement Serializable. Otherwise they can't be passed through the Bundle. Alternatively, you can use ints or Strings as identifiers and use the value to pick which AnkoComponent to show.
All though, the absolutely easiest way is just creating one activity per component.
TL;DR: AnkoComponent is not an Activity, meaning you can't use intents into it. You have to use an Activity, but using Serializable enables you to pass the component through a bundle to an Activity made for manual creation of multiple AnkoComponents without specifying specific types.

Related

Is it mandatory to create inner classes to handle events in DataBinding?

I was learning about DataBinding, particularly the section about handling events and click events. Now, I noticed in a few YouTube tutorials that I instructors were mainly using an inner class to handle these events. However, earlier, I'd written this code that implemented the View.OnClickListener and directly allowed me to handle click events.
Here it is:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private ActivityMainBinding activityMainBinding;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activityMainBinding = DataBindingUtil.setContentView(this, R.layout.activity_main);
activityMainBinding.enterButton.setOnClickListener(this);
}
#Override
public void onClick(View view) {
if (view == activityMainBinding.enterButton) {
String name = activityMainBinding.nameEditText.getText().toString();
String email = activityMainBinding.emailEditText.getText().toString();
String country = activityMainBinding.countryEditText.getText().toString();
User user = new User(name, email, country);
activityMainBinding.setUser(user);
}
}
}
And this works.
I was wondering, is this form of handling click events not considered a good practice? I checked through a few more tutorials and they all used inner classes, thereby causing this doubt.
Thanks for any help.
Q: Is it necessary to create inner classes?
A: No, absolutely not. It's merely a useful convention :)
Q: Is this form of handling click events ... a good practice?
A: In general, any individual "class" should do "one thing". The class's properties and its methods should match the class's "abstraction".
For example, an "Automobile" class should probably not have an "onClick()" method. Even if your "Automobile" class implementation might have a "button", with an "onClick()" method.
Or your "Automobile" might have a dozen different buttons. In that case, I'd definitely prefer to see a dozen anonymous inner classes, one for each button. It would be shorter; it would be cleaner.
In your example, however, I don't see any problem. It looks fine :)

Getting null value in fragment spinner

I am trying to use filter in Fragment and implementing the dialog fragment.
This is the class that I am using
public class HomeFragment extends Fragment implements
FilterDialogFragment.FilterListener,
PostAdapter2.OnPostSelectedListener{ detail code }
this the dialogfragment based class for spinner choosing options
public class FilterDialogFragment extends DialogFragment
this method is called upon clicking the filter button, which pops up dialog for spinner options of the filter
Declared
private FilterDialogFragment mFilterDialog;
in onCreateView
mFilterDialog = new FilterDialogFragment();
Method to call
public void onFilterClicked(){
mFilterDialog.show(getSupportFragmentManager(), FilterDialogFragment.TAG);
}
after this upon selecting the spinner option and clicking apply this method is called in which mFilterListener is null which should not be the case
public interface FilterListener {
void onFilter(Filters filters);
}
FilterListener mFilterListener;
public void onSearchClicked() {
Log.d("Message", String.valueOf(mFilterListener));
if (mFilterListener != null) {
Log.d("Message", "New 55555");
mFilterListener.onFilter(getFilters());
}
dismiss();
}
please assist me to solve this problem. if anymore details are required please let me know
attach method in FilterDialogFragement
public void onAttach(Context context) {
super.onAttach(context);
Log.d("Message", "New 6666666");
Log.d("Message", String.valueOf(mFilterListener));
if (context instanceof FilterListener) {
// Log.d("Message", String.valueOf(mFilterListener));
mFilterListener = (FilterListener) context;
}
}
You are attempting to mimic this library implementation: Friendly Eats.
However, you do not copy it wholesale, mainly in that you choose to use HomeFragment which implements FilterDialogFragment.FilterListener to launch FilterDialogFragment, rather than the library's MainActivity. This is the cause of your null pointer.
This is due to how getSupportFragmentManager() works. If you look at Android's documentation for this, you will see it says
Return the FragmentManager for interacting with fragments associated with this activity. (My Bolding)
When you call mFilterDialog.show(getSupportFragmentManager(), FilterDialogFragment.TAG); inside HomeFragment, you are actually calling whatever Activity that is the parent of HomeFragment to launch the new FilterDialogFragment. You could double check this by checking if, in onAttach(Context context) inside HomeFragment, if context instanceof HomeFragment. I do not think it will return true.
To solve this, and without changing your use of HomeFragment, you could simply pass an instance of HomeFragment itself, or a separate implementation of FilterDialogFragment.FilterListener (which I'd prefer if you do not need to use anything from HomeFragment other than the listener) to your FilterDialogFragment instance on creation or before you launch it.
For example, you could create a public setter like so:
private FilterListener mFilterListener;
public void setFilterListener(FilterListener filterListener){
mFilterListener = filterListener;
}
and then in your HomeFragment onCreateView(), you do this:
mFilterDialog = new FilterDialogFragment();
//Or preferably, an anonymous/named implementing instance of the interface only.
mFilterDialog.setFilterListener(this);
Doing so would not rely on the Android framework to provide the initialisation of your field, and does not require you to either change your Activity or HomeFragment you are currently using.
I assume, that u didnt set the listener in a mFilterDialog, so thats why its null

Android: Updating textviews in multiple activities

I need some pointers on doing the following:
lets say i have 10/20 (number doesn't matter) of activities.
each of these activities has a textview that should work like a counter.
each of these activities has a button to go to the next activity.
this counter starts when the app is launched, and increment itself every second.
So what i did so far is:
have in my main activity a method that instantiate a class that extends Thread.
In that class in the run() method, i increment a variable when a second passes.
Now i'm stuck on what i should do next. Any pointers would be appreciated thanks.
Edit: i need a way to communicate from inside the run method, to whichever activity is now currently on screen, to update its textview.
Just a bit of theory here for standard Object Oriented Programming : stick to the recommended principles like Loose Coupling which makes your project code less tied to each other. You can read more on that later.
Now, using Events, you can setup a system that is synonymous with the natural Publisher/Subscriber design pattern. Like this:
The activity that needs to notify the other activities is called Publisher and the other activities that need to be notified are called Subscribers.
From here:
There are already built and tested libraries to do Events in android. Like my favorite EventBus.
Step 1 Add this line to your app-level build.gradle file:
compile 'org.greenrobot:eventbus:3.0.0'
Then create a simple Plain Old Java Object aka POJO class like this:
public class UpdateTextViewEvent{
private String textToShow;
public UpdateTextViewEvent(String text){
this.textToShow = text;
}
//add your public getters and setters here
}
Step 2 Notify others:
When you want to notify anyone of the changes, you simply called this method:
EventBus.getDefault().post(new UpdateTextViewEvent("Some new Text"));
Step 3 Receive notifications
For those who want to be notified of this event, simply do this:
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);
}
NOTE: to actually handle the event:
#Subscribe
public void onEvent(UpdateTextViewEvent event){
String text = event.getTextToShow();
//now you can show by setting accordingly on the TextView;
}
This is so much easier to do, do decouple your code by eliminating static references in your different activities
I hope this helps! Good luck!
make that Textview in second class as
public static Textview text;
and call it in main activity as
SecondActivity obj=new SecondActivity();
obj.text.settext("");
You can create one another activity e.g. BaseActivity extend with Activity class and your all 10/20 activity extends with created BaseActivity Class.
You can use your textview with protected access specifiers.
What you need to do is inside the counter class, create an a method and passed in a TextView as the parameter. Then create an int variable and set the counter as the instance:
Like this
public static class Counter extends Thread{
private static int x;
#Override
public void run(){
x = counter;
}
public void setCounter(TextView tv){
tv.setText(String.valueOf(x));
}
}
Now call this method setCounter(TextView) in all the activity's onCreate() method you'll like to display the counter, and passed in your the layout TextView as the argument. Like this
...
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState):
....
TextView cTextView = (TextView)findViewById(R.id.texT1);
Counter c = new Counter();
c.setCounter(cTextView);
}

Pass data from activity to a non-fragment/activity class without using an interface

public boolean getFavourite(Winkel winkel){
boolean isFavourite = false;
MyDBHandler dbHandler = new MyDBHandler(context,null,null,1);
User user = dbHandler.loggedIn(); //This method isn't usable anymore
isFavourite = dbHandler.isFavourite(user,winkel);
return isFavourite;
}
I want to change this code to:
public boolean getFavourite(Winkel winkel){
boolean isFavourite = false;
MyDBHandler dbHandler = new MyDBHandler(context,null,null,1);
isFavourite = dbHandler.isFavourite(user,winkel);
return isFavourite;
}
I was using my database to keep track of which user was logged in at first, but it was really easy to change this by simply sending the data from my first activity to my second.
#Override
public void login(User user){
Intent intent = new Intent(this, SecondActivity.class);
intent.putExtra("Username",user.getUserName());
startActivity(intent);
}
Then, in my second activity, I could call all the database functions by using this:
user.setUserName(intent.getStringExtra("Username"));
(Because I need the username as a key for my database.
However, my activity has three fragments, two of which use my adaptor for a recyclerview. These fragments implement the interface that is provided in my adaptor class, which is this one (just showing this to be complete, what the interface is is irrelevant, fact is that my fragments have to implement this interface).
public interface ItemCLickCallback {
void onItemClick(int p);
void onSecItemClick(int p);
}
Now, I need the username in my adaptor class. It has to come from my activity (because thats where its stored), and I can't use an interface because I'm already using one and I would have to override the methods in every fragment.
I could pass the data from my activity to all the fragments using a Bundle, then pass that along to the adaptor when creating it in my fragments. But that seems like a lot of excessive code. Any simpler way to do this? Thank you
EDIT:
In Adaptor class:
private CheckFavourite checkFavourite;
public void setCheckFavourite(final CheckFavourite checkFavourite){
this.checkFavourite = checkFavourite;
}
boolean isFavourite = checkFavourite.getFavourite(winkel);
public interface CheckFavourite{
boolean getFavourite(Winkel winkel);
}
Fragment:
public class Overview extends Fragment implements Adaptor.ItemCLickCallback
Activity:
public class SecondActivity extends AppCompatActivity implements Adaptor.CheckFavourite
Activity has to implement the seconde interface, fragment implements the second. But both of them are on the same adaptor instance.
What I need to do (in my activity basically):
adaptor.setCheckFavourite(this);
But I can't because I don't have the adaptor there, it gets created in my fragment.
I tried:
adaptor.setCheckFavourite(this.getActivity());
in my fragment, but that gives me an an error:
Error:(60, 51) error: incompatible types: FragmentActivity cannot be converted to CheckFavourite.
However
adaptor.setItemCLickCallback(this);
is working perfectly fine in my fragment (the other interface). How do I fix this?
:) You can use multiple inheritance. To be able to make a class implement multiple interfaces we need can have something like this:
public class MyClass extends MyOtherClass implements MyInterface1, MyInterface2, ... , MyInterfaceN {
// Need to implement all interface methods here
}

Android: Referring to a string resource when defining a log name

In my Android app, I want to use a single variable for the log name in multiple files. At the moment, I'm specifying it separately in each file, e.g.
public final String LOG_NAME = "LogName";
Log.d(LOG_NAME, "Logged output);
I've tried this:
public final String LOG_NAME = (String) getText(R.string.app_name_nospaces);
And while this works in generally most of my files, Eclipse complains about one of them:
The method getText(int) is undefined
for the type DatabaseManager
I've made sure I'm definitely importing android.content.Context in that file. If I tell it exactly where to find getText:
Multiple markers at this line
- Cannot make a static reference to the non-static method getText(int)
from the type Context
- The method getText(int) is undefined for the type DatabaseManager
I'm sure I've committed a glaringly obvious n00b error, but I just can't see it! Thanks for all help: if any other code snippets would help, let me know.
That's because getText is a method of Context. It does not matter if you import the Context class; what matters is that you invoke that method from a Context (for instance, the Activity class is a Context (it inherits Context)).
In that case, what I'd recommend, is creating a Application class that returns the context you want. Here I explain how to do it. After that you can do something like:
public final String LOG_NAME = (String) App.getContext().getText(R.string.app_name_nospaces);
Depending on what sort of 'files' you are using, you can define a TAG that is used.
For example, when I create an app, I like to create a base class for my Activity classes...
Suppose my app is called 'Wibble', and my package is com.mydomain.Wibble...I create my base Activity like so...
package com.mydomain.Wibble
public class WibbleActivity extends Activity {
final protected String TAG = this.getClass().getName();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// I'll explain how this next line works later
android.util.Log.d(TAG, "Entered onCreate()...");
}
}
Now suppose I derive an activity as follows...
package com.mydomain.Wibble
public class SomeActivity extends WibbleActivity {
#Override
protexted void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Don't Log "Entered onCreate()..." - WibbleActivity does it for me
android.util.Log.d(TAG, "SomeText");
}
}
Then I derive another Activity...
package com.mydomain.Wibble
public class SomeOtherActivity extends WibbleActivity {
#Override
protexted void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Don't Log "Entered onCreate()..." - WibbleActivity does it for me
android.util.Log.d(TAG, "SomeOtherText");
}
When onCreate() is called for SomeActivity, the output will be...
com.mydomain.Wibble.SomeActivity Entered onCreate()...
com.mydomain.Wibble.SomeActivity SomeText
...when onCreate() is called for SomeOtherActivity however, the output will be...
com.mydomain.Wibble.SomeOtherActivity Entered onCreate()...
com.mydomain.Wibble.SomeOtherActivity SomeOtherText
Neither activity needs to know specifics through an explicit string and the package name is prefixed. Obviously it will only work in certain situations but I find it useful.

Categories