Can the parent view be notified when a child is created - java

In android UI can be created dynamically so is there a way to know if a child was created to a parent ?
For example, if i have a linear layout as a parent and i dynamically create a child button. Is there away to notify the parent ?

Tal Kanel's version will work, but to avoid repeating code, I'd suggest using a HierarchyChangeListener:
LinearLayout ll = (LinearLayout)findViewById(R.id.mylinearlayout);
ll.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
public void onChildViewAdded(View parent, View child) {
//handle the logic for an added child here
}
public void onChildViewRemoved(View parent, View child) {
//optionally, handle logic for a removed child
}
});

It's simple - if you have LinearLayout name linearLayout1, the only why to add child to him is
by calling the linearLayout.addView(View child) method.
so, you know exactly when the child added: it's can be only after this method called :)
example:
linearLayout1.addView(view);
doWhatYouWantToDoWhenChildAdded();

Related

Nested Child Fragment does NOT observe LiveData properly

I have a Parent Fragment, called WaterFountainFragment, that has a nested Fragment progamatically inflated inside a FrameLayout that is dependant of an user's choice in a RadioGroup. If the user chooses one option, it inflates one Child and, when chosen the other option, it inflates another child (in the name of being the most concise as possible, I'll only list one of those child fragments, since the problem happens in both of them).
The user enters the data he wants and save it on the database, using LiveData and Room dependencies to do so. However, if the user wants to go back and check which data was saved in an specific entry, then we start to face trouble.
The problem is, the saved data is shown in the Parent Fragment but, unfortunately, it does NOT load inside the child fragment nested on it.
First, let me show the parent class in which I think everything is working normally:
WaterFountainFragment Class
public class WaterFountainFragment extends Fragment {
(...)
#Override
public void onStart() {
super.onStart();
//modelEntry is a ViewModel, which class ViewModelEntry will be shown later
modelEntry = new ViewModelEntry(requireActivity().getApplication());
//The ID of an entry is sent to this fragment through a bundle (via setArgument());
if (waterFountainBundle.getInt(FOUNTAIN_ID) > 0) {
//getOneWaterFountain is a method inside modelEntry to obtain an entry from the database through
//LiveData and Room
modelEntry.getOneWaterFountain(waterFountainBundle.getInt(FOUNTAIN_ID))
.observe(getViewLifecycleOwner(), this::loadFountainInfo);
}
}
//this is a RadioGroup Listener where it inflates the specific child fragment inside a FrameLayout
public void typeFountainListener(RadioGroup group, int checkedID) {
int index = getCheckedFountainType(group);
switch (index) {
case 0:
getChildFragmentManager().beginTransaction()
.replace(R.id.water_fountain_info, new WaterFountainSpoutFragment()).commit();
break;
case 1:
getChildFragmentManager().beginTransaction()
.replace(R.id.water_fountain_info, new WaterFountainOtherFragment()).commit();
break;
default:
break;
}
}
//method to fill all fields inside this fragment
private void loadFountainInfo(WaterFountainEntry waterFountain) {
fountainLocationValue.setText(waterFountain.getFountainLocation());
typeWaterFountain
.check(typeWaterFountain.getChildAt(waterFountain.getTypeWaterFountain()).getId());
if (waterFountain.getFountainTypeObs() != null)
fountainTypeObsValue.setText(waterFountain.getFountainTypeObs());
//At this point the RadioGroupListener is active already and, when checking a RadioButton, it will
//inflate the child and send a fragmentResult to this child fragment
getChildFragmentManager()
.setFragmentResult(InspectionActivity.LOAD_CHILD_DATA, waterFountainBundle);
}
}
Now we have one of the 2 nested child fragments that are inflated:
WaterFountainSpoutFragment Class
public class WaterFountainSpoutFragment extends Fragment {
#Override
public void onStart() {
super.onStart();
//the ViewModel is instantiated here with the same name and method as the
//one instantiated in the ParentFragment
modelEntry = new ViewModelEntry(requireActivity().getApplication());
//FragmentResultListener is active and it does recieve the signal from the parent Fragment.
getParentFragmentManager()
.setFragmentResultListener(InspectionActivity.LOAD_CHILD_DATA, this, (key, bundle) ->
//However, inside this resultListener, this observer seems to not be triggered
modelEntry.getOneWaterFountain(bundle.getInt(WaterFountainFragment.FOUNTAIN_ID))
.observe(getViewLifecycleOwner(), this::loadSpoutFountainData)
);
}
}
Here we have the ViewModel, the repository and the Dao classes/interfaces that were used on those 2 classes above
ViewModelEntry class
public class ViewModelEntry extends AndroidViewModel {
public ViewModelEntry(#NonNull Application application) {
super(application);
repository = new ReportRepository(application);
allEntries = repository.getAllSchoolEntries();
}
public LiveData<WaterFountainEntry> getOneWaterFountain(int waterFountainID) {
return repository.getOneWaterFountain(waterFountainID);
}
}
ReportRepository Class
public class ReportRepository {
private ReportDatabase db;
private final WaterFountainDao waterFountainDao;
public ReportRepository(Application application) {
waterFountainDao = db.waterFountainDao();
}
public LiveData<WaterFountainEntry> getOneWaterFountain(int waterFountainID) {
return waterFountainDao.getOneWaterFountain(waterFountainID);
}
}
WaterFountainDao Interface
#Dao
public interface WaterFountainDao {
#Query("SELECT * FROM WaterFountainEntry WHERE waterFountainID == :waterFountain")
LiveData<WaterFountainEntry> getOneWaterFountain(int waterFountain);
}
What I know/tested so far
Using a Toast, I confirmed that getParentFragmentManager().setFragmentResultListener() is being called. Even more so, the bundle recieve the right data.
If I use the modelEntry.getOneWaterFountain(bundle.getInt(WaterFountainFragment.FOUNTAIN_ID)).observe(getViewLifecycleOwner(), this::loadSpoutFountainData) outside the resultListener, it does load the correct data into the child Fragment fields.
The data entered by the user IS being stored in the database. I confirmed that using the Database Inspector, so it is not a case where "the data is not being stored properly, hence why is not loading".
I use the same method in other Parent/Child Fragments, using the same format of resultListener and it DOES load the data.
Using another method for creating this ViewModel, like modelEntry = new ViewModelProvider.AndroidViewModelFactory(requireActivity().getApplication()).create(ViewModelEntry.class); in both parent and child fragments results in the same problem
What I SUPPOSE it might be the case
I have wondered that it might be a situation where I am choosing the wrong LyfecycleOwner but I don't know if that is the case, mainly because of what I put on item 4.
Any help would be greatly appreciated.

Android. Recycler view with mutiple view types

I have recyclerView with 2 view types (item and FOOTER). I need to show my footer on the bottom of screen even if no items or items size is 1. Is it possible to implement it? Now my footer is showing after last item, but I need show footer always on the bottom.
You need a data structure that allows you to do so, and then you need the view holders for supporting it, once that is done handling the conditional flows on the adapter and should be good to go.
Usually, in Kotlin we use a sealed class which allows very good type control
sealed class AdapterRow {
data class RegularItem(/*your values*/) : AdapterRow()
data class FooterItem(/*your values*/) : AdapterRow()
//if the footer is always the same maybe object FooterItem : AdapterRow() is more suitable
}
It's a nice "trick" to have the sealed descendants inside so that way the sealed parent makes a domain space name, then you call them like this AdapterRow.RegularItem(...). If you don't like that, the sealed class has one constraint, descendants must be on the same file.
Then you need a view holder for supporting each type (view holder and the view in the layout if needed). In this case we are gonna use an abstract class to take advantage of the polymorphism and abstract methods
//you could use binding here and then the implementation define the binding type
abstract class BaseViewHolder(view: View) : RecyclerView.ViewHolder(view) {
abstract fun bind(row: AdapterRow)
}
And then the children:
class RegularItemViewHolder(view: View) : BaseViewHolder(view) {
override fun bind(row: AdapterRow) {
//validate the data you are receiving is correct
if (row !is AdapterRow.RegularItem) return
//do your data bindings, view findings or whatever
}
}
With the above you can deduce the other view holder, now the adapter methods
getItemViewType(position: Int) {
when(getItem(position)) {
is AdapterRow.RegularItem -> R.layout.REGULAR_ITEM_LAYOUT
is AdapterRow. FooterItem -> R.layout.FOOTER_ITEM_LAYOUT
}
}
onCreateViewHolder(...) {
return when (viewType) {
R.layout.REGULAR_ITEM_LAYOUT -> {
RegularItemViewHolder(...)
}
R.layout.FOOTER_ITEM_LAYOUT {
//same but for footer
}
else -> throw RuntimeException("Unsupported view Holder)
}
}
onBindViewHolder(...) {
holder.bind(getItem(position))
}
The data construction part is the last thing, in somewhere else, your fragment, the view model, etc, you have to create the data structure as you need. By example:
fun makeRows(texts: List<String>): List<AdapterRow> {
val rows = mutableListOf<AdapterRow>()
texts.forEach { text ->
//do some mapping from what ever is your source in this case strings
AdapterRow.RegularItem(...text)
}
//so it doesn't matter if the source of data is empty at the end you always add the footer
rows.add(AdapterRow.FooterItem)
}
And then is just passing the data to the adapter, if you are using ListAdapter
val rows = someClass.makeRows(....)
yourAdapter.submitList(rows)

How to generically retrieve all children of a certain type in a root layout?

What I mean by my question (if I stated it ambiguously, as I couldn't find an answer to my question) is to take a root layout, get all children of that layout, and perform a callback on any that are an instanceof the specified type.
Now, I can do it in a fixed way, easily by doing something like...
RelativeLayout root = (RelativeLayout) findViewById(R.id.root_layout);
for(int i = 0; i <= root.getChildCount(); i++){
View v = root.getChildAt(i);
if(v instanceof CustomLayout){
// Do Callback on view.
}
}
Thing is, I want to make it more generic. I should be able to use any layout, and check to see if it is an instance of any layout. In particular, I want it generic enough to be used with anything (if this is even possible). Of course I don't mind stopping at just settling for layouts.
I want to build a collection of these children and return them, if possible of the same type. I haven't done Java in a long while so I'm very rusty, but I was thinking of using reflection to accomplish this. Is this at all possible?
If I pass the class of the type I want, is it possible?
Edit:
I didn't see dtann's answer before, must have missed it, but I did it on my own and it looks very similar to his. My implementation went something along the lines of this
public static abstract class CallbackOnRootChildren<T> {
#SuppressWarnings("unchecked")
public void callOnChildren(Class<T> clazz, ViewGroup root) {
for(int i = 0; i < root.getChildCount(); i++){
View v = root.getChildAt(i);
if(v instanceof ViewGroup){
callOnChildren(clazz, (ViewGroup) v);
}
if(clazz.isAssignableFrom(v.getClass())){
// The check to see if it is assignable ensures it's type safe.
onChild((T) v);
}
}
}
public abstract void onChild(T child);
}
Difference is that mine relies on callbacks and whatnot, but overall same concept.
Try the following code:
public <T> List<T> getViewsByClass(View rootView, Class<T> targetClass) {
List<T> items = new ArrayList<>();
getViewsByClassRecursive(items,rootView,targetClass);
return items;
}
private void getViewsByClassRecursive(List items, View view, Class clazz) {
if (view.getClass().equals(clazz)) {
Log.d("TAG","Found " + view.getClass().getSimpleName());
items.add(view);
}
if (view instanceof ViewGroup) {
ViewGroup viewGroup = (ViewGroup)view;
if (viewGroup.getChildCount() > 0) {
for (int i = 0; i < viewGroup.getChildCount(); i++) {
getViewsByClassRecursive(items, viewGroup.getChildAt(i), clazz);
}
}
}
}
Call getViewsByClass and pass in the root layout and target class. You should receive back a list of all the views that are instance of the target class. This would include the root layout itself if its also an instance of the target class. This method will search the entire view tree of the root layout.
There is no generic way for this. If anyone does this will simply do the similarly.
Views in viewgroup are kept in field like (source-code):
// Child views of this ViewGroup
private View[] mChildren;
// Number of valid children in the mChildren array, the rest should be null or not
// considered as children
private int mChildrenCount;
See documentation

Reloading Eclipse view

I have a plugin with a view that creates a tableviewer based on different files found in the selected project (the workspace has more than one project loaded). My problem is that when I try to reload the view the information remains the same as on the first run after Eclipse started.
What should I do in order to reload the content provider everytime I reload the view ?
To be told about which part is active you need to use IPartListener2. Make your ViewPart implement IPartListener2.
Set up the listener in the createPartControl:
#Override
public void createPartControl(final Composite parent)
{
....
getSite().getWorkbenchWindow().getPartService().addPartListener(this);
}
Remove the listener in dispose:
#Override
public void dispose()
{
super.dispose();
...
getSite().getWorkbenchWindow().getPartService().removePartListener(this);
}
You will have to implement the various methods of IPartListener, most of these don't need to do anything, the partVisible method is called when your view (or any other part) is shown:
#Override
public void partVisible(final IWorkbenchPartReference ref)
{
if (ref.getId().equals("your view id"))
{
// Your view has become visible ... add code here to update the table
}
}
This is how my partVisible looks:
public void partVisible(IWorkbenchPartReference partRef) {
// TODO Auto-generated method stub
if (partRef.getId().equals("view id taken from extensions"))
{
getWorkspacePath();
viewer.remove(TableContent.INSTANCE.getRow());
viewer.setInput(TableContent.INSTANCE.updateContentProvider());
viewer.refresh();
}
}
The path is updated (i've displayed the path in the view) but the content of the table isn't....updateContentProvider contains the call of the functions that need to parse some files in the selected project....

How to get current content view in Android programming?

I know that I can set the content of the view in an Android app by saying setContentView(int). Is there a function I can use to know what the current content view is? I don't know if that makes any sense, but what I'm looking for is a function called, say, getContentView that returns an int.
Ideally, it would look like this:
setContentView(R.layout.main); // sets the content view to main.xml
int contentView = getContentView(); // does this function exist?
How would I do that?
Citing Any easy, generic way in Android to get the root View of a layout?
This answer and comments give one method: [Get root view from current activity
findViewById(android.R.id.content)
Given any view in your hierarchy you can also call:
view.getRootView()
to obtain the root view of that hierarchy.
The "decor view" can also be obtained via getWindow().getDecorView(). This is the root of the view hierarchy and the point where it attaches to the window, but I'm not sure you want to be messing with it directly.
You can do making a setter and getter of current view by id only
private int currentViewId = -1;
public void setCurrentViewById(int id)
{
setContentView(id);
currentViewId = id;
}
public int getCurrentViewById()
{
return currentViewId;
}
And then in
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setCurrentViewById(R.layout.main_layout);
}
Hope this helps.
In an Activity, you can do
View rootView = null;
View currentFocus = getWindow().getCurrentFocus();
if (currentFocus != null)
rootView = currentFocus.getRootView();
As described above, there is also
View decorView = getWindow().getDecorView();
as well as
View decorView = getWindow().peekDecorView();
The difference between the latter two is that peekDecorView() may return null if the decor view has not been created yet, whereas getDecorView() will create a new decor view if none exists (yet). The first example may also return null if no view currently has focus.
I haven't tried out whether the root view and the decor view are the same instance. Based on the documentation, though, I would assume they are, and it could be easily verified with a few lines of code.
if you have two content views then you can put a tag inside the relative layout of each one. and then get the view by tag name. if tag name is the one desire then blablabla. Hope this help for whoever is searching for a solution.

Categories