I'm attempting to use fragments to give a different display depending on whether the app is being run on a phone or a tablet - as fragments are supposed to be used.
I have the following XML for my layouts:
layout/main.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<RelativeLayout
android:id="#+id/list_fragment_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
layout-large/main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:baselineAligned="false" >
<RelativeLayout
android:id="#+id/list_fragment_view"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2" />
<RelativeLayout
android:id="#+id/sales_agreement_fragment_view"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="7" />
</LinearLayout>
I havetried using FrameLayouts instead of the RelativeLayouts in the second example - it didn't seem to make any difference.
My Activity onCreate() code is as follows:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
FragmentManager manager = getFragmentManager();
View salesAgreementLayout = findViewById(R.id.sales_agreement_fragment_view);
Fragment formsListFragment = new KingdomSpasFormsListFragment();
FragmentTransaction fragmentTransaction = manager.beginTransaction();
fragmentTransaction.add(R.id.list_fragment_view,formsListFragment);
if (salesAgreementLayout != null) {
Fragment salesAgreementFragment = new SalesAgreementFragment();
fragmentTransaction.add(R.id.sales_agreement_fragment_view, salesAgreementFragment);
}
fragmentTransaction.commit();
}
However, running this (whether on a phone or a tablet results in the following error:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.kingdomspas.android.kingdomspasforms/com.kingdomspas.android.kingdomspasforms.activities.KingdomSpasFormsActivity}: java.lang.IllegalArgumentException: No view found for id 0x7f070000 (com.kingdomspas.android.kingdomspasforms:id/list_fragment_view) for fragment KingdomSpasFormsListFragment{527f671c #0 id=0x7f070000}
I've Googled this extensively as it seems to be a common problem. The main solution, however, seem to be that the original layout specified in setContentView() doesn't contain the Views later referenced in the add() methods. However, this is definitely not the problem in my case (unless I'm being ridiculously blind - I've checked and re-checked).
In fairness, I'm not actually certain that this is the best UI design for my app - I'm wondering about tabs perhaps - but, either way, I'd really like to understand what the problem is here and what I'm doing wrong - else I'm likely doomed to repeat it in the future.
Here is a great tutorial on multipane fragments for android. http://www.vogella.com/tutorials/AndroidFragments/article.html
With regards to your problem if you add static fragments in your layout it will solve your problem. If you have a definite need for dynamic fragments I would suggest doing a check for what sort of device that application is running on before you find the view i.e.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
FragmentManager manager = getFragmentManager();
View salesAgreementLayout = findViewById(R.id.sales_agreement_fragment_view);
Fragment formsListFragment = new KingdomSpasFormsListFragment();
FragmentTransaction fragmentTransaction = manager.beginTransaction();
fragmentTransaction.add(R.id.list_fragment_view,formsListFragment);
if (isTablet()){
Fragment salesAgreementFragment = new SalesAgreementFragment();
fragmentTransaction.add(R.id.sales_agreement_fragment_view, salesAgreementFragment);
}
fragmentTransaction.commit();
}
public boolean isTablet() {
return (getResources().getConfiguration().screenLayout
& Configuration.SCREENLAYOUT_SIZE_MASK)
>= Configuration.SCREENLAYOUT_SIZE_LARGE;
}
the is tablet method was adapted from How to detect device is Android phone or Android tablet?
Related
I want to add two fragments when the device is tablet(landscape). But i get the next Error:
java.lang.IllegalArgumentException: No view found for id 0x7f0d006d (com.example.placesearch1:id/container2) for fragment FragMap
I created two layouts : main_activity(for phone):
<FrameLayout
android:id="#+id/container1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true">
</FrameLayout>
and main_activity (for tablet landscape - activity_main.xml(sw600dp-land) :
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_alignParentStart="true"
>
<FrameLayout
android:id="#+id/container1"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">
</FrameLayout>
<FrameLayout
android:id="#+id/container2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1">
</FrameLayout>
</LinearLayout>
I have RecycleView of places(main_activity layout in container1). When i click the item, i get an latitude and longitude of a place and pass the values with bundle to Map fragment and put the map into one of the containers : if device is phone then in container1, but if device is tablet then in container2.
If the device is phone it works perfectly, but if the device is tablet, then i get an error.
in GetPlace method :
Bundle bundle = new Bundle();
bundle.putDouble("lat", place.getLat());
bundle.putDouble("lng", place.getLng());
FragMap fragMap = new FragMap();
fragMap.setArguments(bundle);
if(getResources().getBoolean(R.bool.isTab)) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.container1, new ListFragment())
.replace(R.id.container2, fragMap)
.addToBackStack(null)
.commit();
}
else {
getSupportFragmentManager().beginTransaction()
.replace(R.id.container1, fragMap)
.addToBackStack(null)
.commit();
}
usually when you are designing the xml for phone or tablet the file should be same and also in both xml the content should be same. But here in your case for phone there is only one framelayout but for tablet why two framelayout
Remove this line from GetPlace method
.replace(R.id.container1, new ListFragment())
It's the first time I'm working on an android wear application. I have implemented a list of items and is all right. Now I would like to show to the user an extra information when the users click in one item in a kind of card like in the image.
My click is working ok:
#Override
public void onClick(WearableListView.ViewHolder viewHolder) {
//Toast.makeText(this, "working", Toast.LENGTH_SHORT).show();
}
But I have no idea how to popup a card when the user click in an item. Any idea?
Thanks!
EDITED:
I have added in my activity_main.xml the code below:
<?xml version="1.0" encoding="utf-8"?>
<android.support.wearable.view.WatchViewStub
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:id="#+id/watch_view_stub"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:rectLayout="#layout/rect_activity_main"
app:roundLayout="#layout/round_activity_main"
tools:context=".MainActivity"
tools:deviceIds="wear">
<android.support.wearable.view.BoxInsetLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_height="match_parent"
android:layout_width="match_parent">
<FrameLayout
android:id="#+id/frame_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_box="bottom">
</FrameLayout>
</android.support.wearable.view.BoxInsetLayout>
</android.support.wearable.view.WatchViewStub>
Then after fill the list I want to allow to the user to click on any item and show the card, so I have added the code below:
#Override
public void onClick(WearableListView.ViewHolder viewHolder) {
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
CardFragment cardFragment = CardFragment.create("title",
"description");
fragmentTransaction.add(R.id.frame_layout, cardFragment);
fragmentTransaction.commit();
}
and I'm getting an error:
No view found for id 0x7f0e0014 (wk.gon250.myApp/frame_layout) for fragment CardFragment{207c1da5 #0 id=0x7f0e0014}
The error make sense but I don't know how to fix the error.
The images you have, are showing Cards and there is code snippets here that shows you how to build a card in your app; it is basically a CardFragment and if you are familiar with building a fragment, it shouldn't come as anything strange for you.
FIXED
I outsourced the Bluetooth scanning to a separate thread which was a failure. :D Having fixed this the code now works. Thanks for all support!
ORIGINAL QUESTION
I'm having troubles with exchanging dynamically added fragments (I use the support library and my minimal API version is 8 (Android 2.2)). In my XML which can be seen below file I have a FrameLayout which contains the fragment.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
android:paddingBottom="#dimen/activity_vertical_margin"
tools:context=".BluetoothConnectionManager">
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/frmlFragmentContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"/>
<android.support.v7.widget.Toolbar
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/tbBottom"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
android:background="#color/hafnertec"
android:layout_alignParentBottom="true">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView android:id="#+id/lblMode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/lblMode"
android:layout_centerInParent="true"/>
<ImageButton android:id="#+id/btnRefresh"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_action_refresh"
android:layout_alignParentRight="true"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_alignRight="#id/lblMode"
android:clickable="false"
android:longClickable="false"
android:onClick="updateData"/>
</RelativeLayout>
</android.support.v7.widget.Toolbar>
</RelativeLayout>
For exchanging the fragments i use a SupportFragmentManager. Furthermore, I also instantiate the two fragments in the onCreate() method. In addition, the fragment for discovering Bluetooth devices is added to the frame layout which works fine.
#Override
protected void onCreate(Bundle savedInstanceState)
{
...
// fetch a FragmentManager used for exchanging the fragments
this.fmFragmentExchanger = this.getSupportFragmentManager();
this.btDiscoveryFragment = new BluetoothDiscoveryFragment();
this.btConnectorFragment = new BluetoothConnectorFragment();
if (this.findViewById(R.id.frmlFragmentContainer) != null)
{
this.fmFragmentExchanger.beginTransaction().add(R.id.frmlFragmentContainer, this.btDiscoveryFragment).disallowAddToBackStack().commit();
}
...
}
Moreover, I've taken care that the fragments extend the Fragment class provided by the support library:
import android.support.v4.app.Fragment;
public class BluetoothDiscoveryFragment extends Fragment implements ...
{
// see: https://developer.android.com/training/basics/fragments/creating.html
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, Nullable Bundle savedInstanceState)
{
return inflater.inflate(R.layout.fragment_connection_bluetooth_device, container, false);
}
Here is my code for exchanging the fragments:
this.fmFragmentExchanger.beginTransaction().replace(R.id.frmlFragmentContainer, this.btConnectorFragment).disallowAddToBackStack().commit();
The class for discovering Bluetooth devices makes use of a BroadcastReceiver which is "connected" with the activity. On stopping the discovery process or when it has finished I unregister this BroadcastReceiver.
However, on exchanging the fragments nothing happens and after some time I get an error caused by SIGABRT:
12-31 15:09:51.621 9790-9795/com.hafnertec.afdbluetooth I/dalvikvm﹕ Wrote stack traces to '/data/anr/traces.txt'
12-31 15:09:53.945 9790-9790/com.hafnertec.afdbluetooth A/libc﹕ Fatal signal 6 (SIGABRT) at 0x000001bd (code=0), thread 9790 (ec.afdbluetooth)
Here you can see the file /data/anr/traces.txt: trace file
I'm running the tests on a Samsung Galaxy S I9000 with Android 4.4.4 (CM SNAPSHOT M12). During the activity's onCreate() method I add the btDiscoveryFragment instance which works fine:
this.fmFragmentExchanger.beginTransaction().add(R.id.frmlFragmentContainer, this.btDiscoveryFragment).disallowAddToBackStack().commit();
Furthermore, adding the instance btConnectorFragment using .add() works. However, it logically causes the btConnectorFragment to be overlayed over the btDiscoveryFragment.
I think you have a typo, causing btDiscoveryFragment to be null:
this.fmFragmentExchanger = this.getSupportFragmentManager();
//add this line
this.btDiscoveryFragment = new BluetoothDiscoveryFragment();
//remove this duplicated line
//this.btConnectorFragment = new BluetoothConnectorFragment();
this.btConnectorFragment = new BluetoothConnectorFragment();
First Basically I need to have layout when application starts.
Second I am getting data from views i have in this layout.
Third Im setting next layout and doing hard code work with data, that I got in step 2.
I can't figure out how to do this.
You can re-use the framework class ViewAnimator without any animations. It's not the prettiest solution, but should get the job done.
main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<ViewAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="fill_parent"
android:layout_width="fill_parent"
android:id="#+id/viewFlipper" />
</LinearLayout>
MyActivity.java:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
viewAnimator = (ViewAnimator)findViewById(R.id.viewFlipper);
viewOne = createViewOne();
viewTwo = createViewTwo();
viewAnimator.addView(viewOne);
viewAnimator.addView(viewTwo);
...
}
private void someMethod() {
... do my background task ...
viewAnimator.setDisplayedChild(1); // viewTwo
}
have you gone through the google notepad tutorials? They explain using a list screen and loading a edit/add screen used to modify you sqlite database
I am developing a simple app where you can see a list of musicians, and when you click on a musician, you get some details about it (genre, top hits and biography).
The functionality that I want to achieve is this: in case you're viewing it on a wide screen, let the list of musicians be on the left and the details should appear on the right (once a musician is clicked); if it's a narrow screen, the details should appear separately on a new screen. The functionality should be accomplished with fragments and Framelayouts.
So I have the activity_main.xml, and I went to Add new resource file -> layout file and I added activity_main.xml (w600dp) which I expect to automatically load on landscape-oriented smartphones or tablets.
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
android:layout_height="match_parent"
android:layout_width="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/muzicari_lista"
android:layout_weight="1">
</FrameLayout>
</LinearLayout>
w600dp/activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal" android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="#+id/lista_muzicara"></FrameLayout>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="#+id/detalji_muzicar"></FrameLayout>
</LinearLayout>
XML's of fragments that contain the list and the details are fairly simple with a few textblocks.
Here's the onCreate method of the mainActivity's class:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
unosi = new ArrayList<Muzicar>(); //let's presume there's something in this list
wideL = false;
FragmentManager fm = getFragmentManager();
//fetching FragmentManager
FrameLayout ldetalji = (FrameLayout)findViewById(R.id.detalji_muzicar);
if(ldetalji!=null){
//layout for wide screens
wideL=true;
FragmentDetalji fd;
fd = (FragmentDetalji)fm.findFragmentById(R.id.detalji_muzicar);
//checking if there is FragmentDetalji already created:
if(fd==null) {
//if not, we're creating it now
fd = new FragmentDetalji();
fm.beginTransaction().replace(R.id.detalji_muzicar, fd).commit();
}
}
FragmentLista fl = (FragmentLista)fm.findFragmentByTag("Lista");
//we're checking if there's already fl created
if(fl==null){
//if it hasn't been created:
fl = new FragmentLista();
Bundle argumenti = new Bundle();
argumenti.putParcelableArrayList("Alista",unosi);
fl.setArguments(argumenti);
fm.beginTransaction().replace(R.id.muzicari_lista, fl).commit();
}else{
//case when we change from portrait to landscape, and there was FragmentDetalji already open
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
}
Classes FragmentDetalji and FragmentLista don't do much special besides utilizing onCreateView (though I can add them if they might be the source of the trouble).
The problem is that no matter what I use for loading this app, activity_main gets loaded in its default form, not the w600dp version! What am I doing wrong?
To provide alternative layout resources, you need to create a sub-directory inside your res folder named layout-sw600dp and place your altered layout in there, with the EXACT name as the other small screen layout. Then your Activity should automatically look in this folder. So if your xml file is titled activity_main.xml it should have the exact same name in the layout-sw600dp folder as well.
You can also supply alternate layouts for landscape by creating a sub-directory called layout-land or layout-sw600dp-land for large screen.
Also note that there are differences between using sw600dp and w600dp -- See this question for more info: Difference between sw600dp and w600dp?