Add views to activity at runtime using MVVM in Android - android

I have many activities in my application. Each activity has many UI elements like EditText, TextView, DatePicker, DropDown etc. Activity has both static components (views statically placed in xml layout) and dynamic components (views added at runtime based on business logic and API response). The UI elements to be added in activity are dynamic. What all UI elements needs to be added, depends on the API response.
I'm thinking about writing a method which takes in the information about what all views to be added as parameter and constructs the view and return it as a LinearLayout with all child views added.
In the code snippet below, rowBuilder is a data class which stores information about the views to be added at runtime. The method will build a vertical linearLayout with all the views needed and return it.
fun getUIFields(context: Context, rowBuilderList: ArrayList<RowBuilder>): LinearLayout {
val rootVerticalLinearLayout: LinearLayout = getRootVerticalLinearLayout(context)
for(i in 0 until rowBuilderList.size) {
val horizontalLinearLayout = getHorizontalLinearLayout(context)
for (j in 0 until rowBuilderList[i].columnBuilderList.size) {
val formFieldInfo = rowBuilderList[i].columnBuilderList[j].formFieldInfo
if (formFieldInfo.formFieldType == FormFieldType.EDIT_TEXT) {
addEditText(formFieldInfo, horizontalLinearLayout, context, i.toString() + j.toString())
} else if (formFieldInfo.formFieldType == FormFieldType.DATE_PICKER) {
addEditTextForDatePicker(formFieldInfo, horizontalLinearLayout, context, i.toString() + j.toString())
}
}
rootVerticalLinearLayout.addView(horizontalLinearLayout, i)
}
return rootVerticalLinearLayout
}
Where do I place the above logic, which requires context? I know we have AndroidViewModel, wherein I can use context. Also, If I write the above logic in ViewModel, the same code needs to be duplicated in every viewmodels. I need to know about a more efficient approach using MVVM
Please help!

Related

Create drag and drop between two RV adapters with data changing using ViewModel

In my project I have a recycler view which used for two cases (two states of entity). I want to make items draggable between two RVs, but the DragEvent.ACTION_DROP changes the state of element which was in under of dropped item. Is it possible to reverse it? Can I get the position of the item where the drop was finished? I have found many other solutions, but in my case the big problem is changing position of items, because they're updating by ViewModel's methods invocation in main fragment.
Maybe I need to choose another way of data showing and create, as example, different adapters with a separate lists?
Code inside the itemView.setOnTouchListener in init block:
override fun onItemLongTap() {
itemView.tag = adapterPosition
val shadowBuilder = MyDragShadowBuilder(itemView)
val item = ClipData.Item(itemView.tag as? CharSequence)
val data = ClipData(
itemView.tag as? CharSequence,
arrayOf(ClipDescription.MIMETYPE_TEXT_PLAIN),
item
)
itemView.startDrag(data, shadowBuilder, itemView, 0)
}
})
val customDrag = View.OnDragListener { view, event ->
if (event.action == DragEvent.ACTION_DROP) {
onItemDrag?.invoke(items!![adapterPosition])
}
true
}
itemView.setOnDragListener(customDrag)
Invocation in main fragment using var onItemDrag: ((Item) -> Unit)? = null:
firstAdapter?.onItemDrag = {
itemViewModel.changeFirstState(it.id)
}
secondAdapter?.onItemDrag = {
itemViewModel.changeSecondState(it.id)
}
I almost forgot to answer on my own question, but I think someone's could have a similar problems, so look.
If you want to use the only one list and update the item's state by DB request, you can just handle "ACTION_DRAG_EXITED" with a lambda at the moment of intersection with another view(using Rect()), but in this case you can't get a position of item which was under(just a fact of collision). This is the case when the RV only shows the data and limits you in actions.
Another solution - create a different lists and replace item's by position changing, then update DB.
Anyway, these are just examples.
In my case, I abandoned the above approaches, because that would be redundant, and only used the intersection check.
So choose the approach that not only does not complicate your code, but also makes it more appropriate and functional.

Android BottomSheet and dynamic Content

I have a bottom sheet, where i can dynamically pass to it the layout id of the layout i want it to show. This works fine! The problem with this is that, to me, it is somewhat cumbersome to assign the listeners I want to every UI element of that layout.
I can also pass the assign a pre-infalted view with all the listeners assigned to a member of the bottomsheet, which also works.
I was wondering if there is a best practice established for this, or any pros/cons in the approaches!
Thanks!
If I understood correctly, You can define a loop on ViewGroup of Inflated and get each of View finally define a onClickListener and set it on views
override fun onViewInflated() {
viewGroupInflated?.forEach {
it?.setOnClickListener(onClickListener)
}
}
private val onClickListener = View.OnClickListener {
when(it.id){
R.id.btn_a -> //Todo an action
R.id.btn_b -> //Todo an action
}
}

BottomSheetDialog/BottomSheetDialogFragment — which to use and how?

I'm working on a Material design app. One feature I want to implement is some kind of a poll. When a user clicks an element of a list, the persistent bottom sheet dialog, which looks like this should show up:
Then, when user clicks any button, this dialog should go away and the modal bottom sheet dialog should show up, providing a user with more information about the list item which was clicked at the beginning. It looks like this:
I can't find any clear explanations about BottomSheetDialog and BottomSheetDialogFragment, and how to use them correctly, even after reading some information about AppCompat dialogs. So, my questions are:
In what way are they different and which one should I use for each
case?
How to get data in the activity about which button was pressed in the dialog?
Any links to the code of implementations or tutorials about using them?
Finally, I've found the solution and it works. Tell me if I'm doing something wrong. It basically works like DialogFragment from this guide, but I've done it a bit different.
1) Their difference is the same as it of DialogFragment and Dialog, and they both are modal. If you need persistent dialog, use BottomSheetBehaviour instead (I found out that both dialogs had to be modal in my app).
2) I have to answer the third question with some code first, and then it will be easy to answer the second one.
3) Create a new public class, which extends BottomSheetDialogFragment, I called it FragmentRandomEventPoll. There are two two things which have to be implemented here.
Override method onCreateView. It is nearly the same as onCreate method in Activities, except for that it returns the View it should inflate:
// We will need it later
private static String headerItem;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_random_event_poll, container, false);
header = (TextView) v.findViewById(R.id.uRnd_fragment_bottom_sheet_poll_header);
skip = (Button) v.findViewById(R.id.uRnd_fragment_bottom_sheet_button_skip);
header.setText(...);
// I implemented View.OnClickListener interface in my class
skip.setOnClickListener(this);
return v;
}
Static method which you can pass necessary data to and get new instance of this class (Probably I could have just used a regular constructor, I'll have to experiment with it a bit more). URandomEventListItem is the data model class.
public static FragmentRandomEventPoll newInstance(URandomEventListItem item) {
FragmentRandomEventPoll fragment = new FragmentRandomEventPoll();
headerItem = item.getHeader();
return fragment;
}
2) To get input events in activity or any other place, define an interface with necessary methods and create setter method for it's instance:
private PollButtonClickListener listener;
public void setListener(PollButtonClickListener listener) {
this.listener = listener;
}
public interface PollButtonClickListener {
void onAnyButtonClick(Object data)
}
And in the place you want to get your data ("dialog_event_poll" tag was specified in the layout):
FragmentRandomEventPoll poll = FragmentRandomEventPoll.newInstance(events.get(id));
poll.setListener(new FragmentRandomEventPoll.PollButtonClickListener() {
#Override
public void onAnyButtonClick(Object data) {
// Do what you want with your data
}
});
poll.show(getSupportFragmentManager(), "dialog_event_poll");
}
If there is anything unclear, my project files could be found on Github.
About handling events from DialogFragment/BottomSheetDialogFragment.
For applications with many activities, this method is great:
context as MyDialogFragmentListener
But I have a problem with an application with single activity and multiple fragments. Since there can be a lot of fragments, it seems like a very bad option to transfer all events to the necessary fragments through the main activity. Therefore, I decided to do this:
private inline fun <reified T> findListeners(): ArrayList<T> {
val listeners = ArrayList<T>()
context?.let {
if (it is T) listeners.add(it)
if (it is AppCompatActivity) {
it.supportFragmentManager.fragments.forEach { fragment ->
if (fragment is T) listeners.add(fragment)
fragment.childFragmentManager.fragments.forEach { childFragment ->
if (childFragment is T) listeners.add(childFragment)
}
}
}
}
return listeners
}
Code in DialogFragment:
private val listeners by lazy { findListeners<MyDialogFragmentListener>() }
Of course, fragments can contain as many other fragments as you like and probably need to be checked through recursion, but in my case this is superfluous.

Typed UI and listeners in android

I would like to improve the way i created the following UI. Currently i am creating each tablerow programmatically according to each object's type attribute.
class objectDTO {
private type; //enum
public boolean isMultiple(){...}
public boolean isSingle(){...}
}
I am trying to find a more dynamic solution like having a class for each type that might not requires programmatically adding layouts and components as i do in the fragment class,
if(objectDTO.isMultiple()) {
//Create TableRow + Multiple radiobuttons
}
else if(objectDTO.isSingle() {
//Create TableRow + Add One Checkbox
{
else {
//Create default invalid object Interface or skip
}
Havind a listadapter and applying the different ui there will just move the design problem to other class.
Early thanks for your contribution
Well, the simple solution for you would be to have a class hierarchy- a base objectDTO class and a child for each type. When you load the object list, have a factory method create the proper type of object. Each type would override a createView method which would create the view for that type. Then your table creation function becomes:
for(objectDTO object : allObjects){
View view = object.createView();
tableView.addView(view, lp);
}
But if you're creating a view for an object type, there's always going to need to be someone that dynamically creates view objects (createView in this case), and there's always going to need to be some function that knows what class to make an object (the factory in this case). Its just a matter of where you want that complexity to be.

Bind RadioGroup inside MvxListView

I've got some very tricky problem. I already tried to search the web and even looked into the MvvmCross sources, but I don't seem to be able to figure it out.
I have an MvxListView with a custom Adapter. The reason is, that depending on the "DataContext" of the current ListItem, I want to display some different view.
The list itself represents some sort of questionnaire. So the items in the list are in the form of
new Question("do you need help?"){
new Answer("yes"),
new Answer("no"),
new Answer("maybe")
}
Now the answers shall be shown as a radio button list.
So in my custom adapter on "GetChildView", I retrieve the view with the radiogroup and then I
"just want to bind that group to my answers" --> so for each answer, there has to be a corresponding radiobutton.
I would love to have the "Answer" object as datacontext for each radiobutton.
radioButton.Bind("Checked", "Chosen"); // where "Chosen" is the boolean property on "Answer"
But it would already be fine if the "Question" object could be the datacontext that I bind to
radioGroup.Bind("CheckedRadioButtonId", "ChosenAnswer"); // where "ChosenAnswer" is an int property
on "Question"
So basically I want to bind my radiobutton to the MvxListItem.DataContext in code inside my customadapter.
But I just cannot figure out how to do that. :/
Can you please give me a hint?
Of course I would love to do the same with a list of checkboxes as soon as multiple answers would be allowed.
Setting the datacontext is easy: just set it :)
What you do is you create a ViewModel called something like QuestionViewModel, which has what you need as a separet ViewModel.
Then create some component to use in your View for the complete questionnaire. Below is some example code for a bindable component.
public class BindableLinearLayout : ClickableLinearLayout, IMvxDataConsumer, IMvxBindingContextOwner
{
public BindableLinearLayout(Orientation orientation, object dataContext)
: base(orientation)
{
BindingContext = new MvxBindingContext();
DataContext = dataContext;
}
public object DataContext { get { return BindingContext.DataContext; }
set { BindingContext.DataContext = value; }
}
public IMvxBindingContext BindingContext { get; set; }
}
In the Questionnaire View, create this component and assign the datacontext (in the above example as a parameter). Then you can create the binding in the normal way:
var bindings2 = layout.CreateBindingSet<BindableLinearLayout, ParagraphViewModel>();
bindings2.Bind(numberText.View).For(t => t.Text).To(vm => vm.Paragraph.Number);
bindings2.Apply();
This code is called for each element you add to the collection, eauch with its own Datacontext.
I known this code is not for a list adapter, but I hope this will give you enough hints how to do this yourself.

Categories

Resources