How to share common layout between activities without fragment - android

Is there any possible way to share layout(part) between activities? For example, in my app, all activities have similar layout, the top part is long operation indicator (a progress bar, hidden when no operation is being executed), the bottom part is for showing errors. Only the middle part is different for all activities. See the picture below.
so my question is, is it possible to reuse the common layout(loading and error part) for all activities in my app? (currently I don't want to use fragment to do it for some reasons)
maybe the layout resources should like this:
layoutfolder
activity_common.xml
activity_one_content.xml
activity_two_content.xml
thanks

You can create an abstract 'base' activity that all your activities extend from, overriding setContentView to merge the base, and sub activity layouts.
This way you can handle all the loading/error code in the base activity, and simply toggle between hiding and showing the views in the sub activities.
The abstract activity:
public abstract class BaseActivity extends Activity {
protected RelativeLayout fullLayout;
protected FrameLayout subActivityContent;
#Override
public void setContentView(int layoutResID) {
fullLayout = (RelativeLayout) getLayoutInflater().inflate(R.layout.activity_base, null); // The base layout
subActivityContent = (FrameLayout) fullLayout.findViewById(R.id.content_frame); // The frame layout where the activity content is placed.
getLayoutInflater().inflate(layoutResID, subActivityContent, true); // Places the activity layout inside the activity content frame.
super.setContentView(fullLayout); // Sets the content view as the merged layouts.
}
}
the layout file:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- The main content view -->
<FrameLayout
android:id="#+id/loading_frame"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<!-- The main content view -->
<FrameLayout
android:id="#+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<FrameLayout
android:id="#+id/error_frame"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>

You could use include in XML to, well.. include the re-useable part of your layout code.
As an example, here's my layout file for the Toolbar I used in my app:
// /res/layout/component_toolbar.xml
<android.support.v7.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:taggr="http://schemas.android.com/apk/res-auto"
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/primary"
android:minHeight="?attr/actionBarSize"
taggr:popupTheme="#style/ThemeOverlay.AppCompat.Light"
taggr:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar" />
Now, say if I want to use that Toolbar again in a different Activity, this is all I'd have to write:
// /res/layout/whatever_layout_this_might_be.xml
<include layout="#layout/component_toolbar" />
Bear in mind that this would only copy the layout - not the actual behavior of said widget/component.
If you want to actually copy all of the aspects (layout, behaviour) I'm afraid Fragment is the only way to go.

Although ActivityGroup is deprecated fro API 13 but if you don't wish to go with fragments then this can be your best choice.
According to documentation, an ActivityGroup is:
A screen that contains and runs multiple embedded activities.
You can find a tutorial here and here Although the mentioned tutorial uses a Tablayout you can replace that with your common layout in XML.
A second Approach could be Reuse the layout with include tag, in this approach you could just reuse your once created common layout everywhere in the app.

Related

Fragment Button is found by Activity's findViewById instead of Fragment's inflated view.findViewById

I'm seeing this strange behavior and couldn't find anything similar to this.
So I have a parent Activity and inside is a Fragment, which I'm including in parent via include element and then in parent's onCreate, create Fragment and replace it with this include layout (Tell me if this is a right way? I was using FrameLayout but then switched to include and defined an id to it).
Activity
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context="com.sourcey.materiallogindemo.CustomerDetailActivity"
tools:ignore="MergeRootFrame">
<com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.appbar.MaterialToolbar />
</com.google.android.material.appbar.AppBarLayout>
<include
android:id="#+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="#id/app_bar"
app:layout_constraintBottom_toTopOf="#id/layout_bottom_bar"
layout="#layout/fragment_customer_detail" />
<androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.bottomappbar.BottomAppBar>
</com.google.android.material.bottomappbar.BottomAppBar>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Fragment
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context="com.sourcey.materiallogindemo.CustomerDetailFragment"
android:layout_height="match_parent"
android:layout_width="match_parent">
<!-- THIS IS THE CULPRIT -->
<com.google.android.material.button.MaterialButton
android:id="#+id/btn_update_position"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<com.google.android.material.button.MaterialButton />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/sku_list"
app:layoutManager="LinearLayoutManager"
tools:context="com.sourcey.materiallogindemo.CustomerDetailFragment"
tools:listitem="#layout/fragment_s_k_u_item" />
</androidx.constraintlayout.widget.ConstraintLayout>
Fragment is inflated correctly but when I do this inside onCreateView
rootView.btn_update_position.setOnClickListener {
// ... log something
}
and press the Button, it doesn't do anything? Even though most findings were led to this suggestion that I should inflate the view and then set onClickListener.
I also tried doing these
rootView.findViewById<MaterialButton>(R.id.btn_update_position).setOnClickListener {
// ... log something
}
and
val button = rootView.findViewById<MaterialButton>(R.id.btn_update_position)
button.setOnClickListener {
// ... log something
}
but none of them works.
I also tried above approaches in onViewCreated to see if maybe I was not getting the reference but no errors were thrown and no reaction was coming.
Only thing that works is this
activity?.findViewById<MaterialButton>(R.id.btn_update_position)
?.setOnClickListener {
// ... log something
}
I'm trying to understand why this happens? Could this be the issue of using include the Fragment?
NOTE I'm not a pro in android just do hobby work in it so don't know very deeply about it.
EDIT As you can see I have a RecyclerView in Fragment layout, I'm inflating the layout and then setting its adapter items which seems to work fine opposed to button.
rootView.sku_list.adapter = Adapter()
I'm bit confused about what you want to do here
First,<include> doesn't create new view, it just include the xml into the parent xml file so basically it still on activity and you need activity to findViewById
Second, about your question what different between FrameLayout and <include>.
With <include> like i said above, it just add xml file to the parent file, the main usage is for re-use layout (you can include it anywhere) .
With FrameLayout, from official doc : "FrameLayout is designed to block out an area on the screen to display a single item". E.g : you want your layout have a header and footer for all screen, only the middle part change so place a frame layout at middle then load different view for each screen, because that flexibility frame layout usually use for display fragment (you can google how to use frame layout for more details)

Multiple Views Vs Multiple Fragments

I'm going to create a complex/normal design that has an empty screen layout & shimmer layout.
my question is to have all in one layout with multiple <include> files is better than doing two fragments and keep replacing them.
more details:
1- make the data screen shown & empty screen hidden as follows:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="#+id/toolbar"
layout="#layout/common_toolbar" />
<include layout="#layout/empty_layout"/>
// my data layout
</LinearLayout>
2- create a FrameLayout and keep change the container for different screens with multiple fragments:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
I need to know the best practice as depending on slow rendering of multiple views.
another question, is that included in for shown/hidden views or it depends on shown ones only as I got confused
and need to apply best practices.
if you attend to reuse some views Fragment is the best, otherwise, if you looking best to render I advise you to use constraint layout and section your views with a group so you can hide and show group
take a look to this one How to group multiple views in a ConstraintLayout

Example of how to use MvxCachingFragmentActivity

Update - Thanks to #Martijn00 and #Plac3Hold3r I have managed to update my app to use the MvxCachingFragmentCompatActivity but it is just not working correctly. I am finding that if I go back sometimes the the ViewModel will be available, but the commands in the view model will be null.
Also if I go back and then forward, some of the buttons don't respond. I assume this is the same issue.
What I really need to know is what the additional functionality the caching activity gives me, and how to use it properly.
Original question follows...
I am hitting a problem where the view model for a fragment is null. I suspect my activity is being cleared up when I open a camera activity. For all my application am using a single activity and all my views are fragments.
When the camera activity is complete, the activity is reconstructed, but one of the fragments view model is null. I am currently using an AppCompatActivity for my single activity, but through my research I should probably be using a MvxCachingFragmentActivity. The problem is I have no idea how I am supposed to use it. I cannot find a clear explanation anywhere.
Has anyone got a working example of how to use the MvxCachingFragmentActivity or the MvxCachingFragmentCompatActivity.
I can't find anywhere where it tells me how I should use it.
I found this other link example but I think it is out of date and the other link given in this example is a 404.
If anyone knows of a simple sample and whether this will work with a single activity please let me know.
Thanks
Setup
MainActivity
Create an activity that inherits from MvxCachingFragmentCompatActivity or MvxCachingFragmentActivity.
[Activity]
public class MainActivity : BaseFragmentActivity<MainContainerViewModel>
{
}
MainContainerViewModel
Create a Viewmodel to associate to the Activity. You will never navigate directly using this Viewmodel. Instead you will navigate to this Viewmodel via the fragments that specifies MainContainerViewModel as their parent's Viewmodel.
public class MainContainerViewModel : MvxViewModel
{
}
XML layout example
Add a layout to your MainActivity. Make sure to include a FrameLayout that has an id. In this case content_frame. This id is important as this is how you will identify the frame where to place your fragment. You specify multiple FrameLayout's if you want more that one fragment for the same view.
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary">
<TextView
android:id="#+id/textview_toolbar_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#android:color/white"
android:layout_gravity="left"
style="#style/TextAppearance.AppCompat.Widget.ActionBar.Title" />
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>
<FrameLayout
android:id="#+id/content_frame"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</android.support.design.widget.CoordinatorLayout>
HomeFragment
In terms of fragments you need to include the MvxFragment attribute, which needs the type of Viewmodel associate to the Activity you want to place your fragment in. Additionally, you need to specify the id for the FrameLayout that is found on the activities layout, where you want to place the fragment.
[MvxFragment(typeof(MainContainerViewModel), Resource.Id.content_frame)]
[Register(nameof(HomeFragment))]
public class HomeFragment : BaseFragment<HomeViewModel>
{
}
Usage
When navigating your can now use ShowViewModel<HomeViewModel>() which will navigate to the home fragment. But, importantly it will first start up the required Activity MainActivity before doing the fragment navigation. This allows for better shared navigation with other platforms which do not require these container Viewmodels, i.e. they get handled automatically via convention.
Notes
You can specify multiple MvxFragment attributes. Is is usefully if you want the same fragment shared under multiple Activities. The Top MvxFragment attribute will be used as the default. If you are currently in the context of any of the other matching MvxFragment attributes then navigation will take place under that activity.
If your Setup.cs is not inheriting from MvxAppCompatSetup or you are using a custom presenter, you need to make sure that you also registering your presenter against IMvxAndroidViewPresenter. This is important as MvxCachingFragmentCompatActivity or MvxCachingFragmentActivity resolve IMvxAndroidViewPresenter in order to navigate to the required fragment.
protected override IMvxAndroidViewPresenter CreateViewPresenter()
{
var mvxFragmentsPresenter = new MvxFragmentsPresenter(AndroidViewAssemblies);
Mvx.RegisterSingleton<IMvxAndroidViewPresenter>(mvxFragmentsPresenter);
return mvxFragmentsPresenter;
}
You can also check out the Sample Repo for an example of this in use.

Android - How to reuse a wrapper layout for different layout XMLs?

I want to make every activity have a same wrapper layout like a drawer or a bottom toolbar. It's easy to create a BaseActivity to reuse the code for all activities. But how can I reuse the XML wrapper for all the different activity layouts instead of adding different contents into a same XML file dynamically?
For example, I have 5 XML layout files. They should have a same wrapper, and after I modify one of them, the others should be changed synchronously. A most simple example could be a drawer:
<android.support.v4.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
// different content here
<android.support.design.widget.NavigationView
android:id="#+id/navigation_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="#layout/drawer_header"
app:menu="#menu/drawer" />
</android.support.v4.widget.DrawerLayout>
You can create an activity and add/remove fragments within it, this way the activity will hold all the common UI components and the distinct views will be in the fragments
http://developer.android.com/training/basics/fragments/index.html
You can create the layout separated and use tag, and, all your activities will inherit a "BaseActivity" which will set the content, and then, in your child activity, it will find the ID for the "content", and replace it.
Does that work for you?
Note that the content may be a simple ViewGroup to be replaced.

Complex Android UI design guidance needed (fragments)

I am developing an applications that is aimed at Tablets and Google TVs. It will be like many standard Google TV applications with a LeftNavBar and a top Search bar that is common to all application screens. It will look something like the following image:
Main Screen
The RED area will be different for all other screens. It may contain data like following screens mockups:
Activity One loaded into main container
Activity Two loaded into main container
So you can see that completely different sections can be loaded in the main area.
Screen 3 can be loaded as a detailed section when selecting any list item in Screen 2 (say in fragment list) OR it can be loaded as a result of selecting a tab (which will appear in LeftNavBar).
Here is how I am trying to implement it.
Step 1. I Created a main Activity with the following XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#9ccc" >
<!-- Top Bar -->
</LinearLayout>
<FrameLayout
android:id="#+id/mainContainer"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<!-- main Red Container that will load other Activities -->
</FrameLayout>
</LinearLayout>
mainContainer is the RED container where I want to load the Activities. LeftNavBar will be added to this Activity as its the parent of All.
Step 2 I created ActivityOne & ActivityTwo with two & three Fragments in them respectively (as shown in above second & third image).
*Step 3 I am trying to load the ActivityOne in main page's mainContainer FrameLayout... But I cannot add it.
I tried by adding the ActivityOne to mainContainer as follows:
View v = (new ActivityOne()).getWindow().getDecorView();
FrameLayout mainContainer = (FrameLayout) findViewById(R.id.mainContainer);
mainContainer.addView(v);
but the getWindow() returns null....
Other issue occurs because all the data comes from a remote services .. so please also suggest how would I be able to hold references to all the loaded Activities in mainContainer in a some kind of stack ... so I can just reload the already loaded activity instead of creating its new instance.. This will be used on BACK button press.
OR
Instead of loading an activity into the above RED container, I should create two Activities each with their own Fragments & a LeftNavBar. This might be easier than the aforementioned approach. or this might be the only solution.... however I feel that saving state for BACK buttons might get messy .. but I will try implementing this
What would you do if you had to create this type of application?
How would you design the UI layout for best performance/practice?
Your suggestions in helping me setting this app's layout are much appreciated.
Disclaimer
This is where fragments can get tricky. The problem would be simple if Activity 1 & 2 had identical layouts so that you could simply attach/detach fragments and use the fragment back stack to unwind.
Because you want 2 unique layouts to house your fragments, things are going to be a little more involved. If at all possible I would try to use the same layout so that you can take the easy path.
As another option, you could use two activities as you outline above and send data back and forth with Intents.
That said, if I really had to implement this solution as written, here is what I would do. Note that I am not advocating this solution but myself do not know of a better way of doing things.
The Solution
Create a FragmentActivity whose view would be Main Screen as you've defined above. The layout for the Main Screen would contain:
Left nav bar
Top bar
2 layouts. layout1 and layout2. These would be contained in a parent layout i.e. RelativeLayout or LinearLayout and would contain the necessary FrameLayout elements for your fragments.
Example using your XML (note, tags are a bit brief):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#9ccc" >
<!-- Top Bar -->
</LinearLayout>
<LinearLayout android:id="#+id/layout1">
<FrameLayout android:id="#+id/listFragment" />
<FrameLayout android:id="#+id/contentFragment" />
</LinearLayout>
<LinearLayout android:id="#+id/layout2">
<FrameLayout android:id="#+id/imageFragment" />
<FrameLayout android:id="#+id/boxFragment1" />
<FrameLayout android:id="#+id/boxFragment2" />
<FrameLayout android:id="#+id/boxFragment3" />
</LinearLayout>
</LinearLayout>
The main idea is that you then show/hide layout1 & layout2 i.e. set android:visibility="gone" based on the state of your app.
The disadvantages of this method are:
Using fragment backstack may be impossible, instead you'll have to track where the user is in your UI flow and manage the back button to show/hide layout
You may need to take special care to attach/detach fragments when you show/hide their parent view to reduce resource consumption while the fragments are invisible
The advantages are:
Easy communication between fragments and the base activity since only 1 activity is used
Re: The nested Fragments problem
To get around the 'nested Fragments' problem in our application where (as you correctly note) Fragments cannot add Fragments I hosted a single templating Fragment under the activity whose only purpose was to define a set of place holders for other fragments to anchor to. When adding further Fragments to the activity past this point I used the templating Fragment's view place holder +#ids to identify the "root" or parent view Id for the Fragment being added.
getSupportFragmentManager().beginTransaction().add(#someIdFromTheTemplateFrag, fragment, fragmentTag).commit();
The Fragment I was adding then knew where to anchor itself in the current layout and of course then went about it's merry way of add it's view. This had the effect of attaching a Fragment to another Fragment hence achieving the desired visual 'nesting'...

Categories

Resources