I am using the following components in my application
Activity
Fragment
WebView
Goal - I don't want to reload the webview on orientation change.
Issue - Currently I see the following error on orientation change "Webpage was not available. The webpage at https://fakeurls.org could not be loaded because net::ERR_CACHE_MISS"
Steps to Reproduce
Application was opened in device portrait mode.
Content of given URL was loaded in webview successfully
Device was turned to landscape mode
Webview shows error
Code - Android application makes a post request in a webview to url https://fakeurls.org. Below is the code for MyActivity and MyFragment class
MyFragment.java
public class MyFragment extends Fragment {
private ProgressBar mProgressBar;
private WebView mWebView;
private String mUrl = null;
private String mPostData = null;
public MyFragment() {
// Required empty public constructor
}
public static MyFragment newInstance(#android.support.annotation.NonNull String url, String postData) {
MyFragment MyFragment = new MyFragment();
Bundle bundle = new Bundle();
bundle.putString("URL", url);
bundle.putString("POSTDATA", postData);
MyFragment.setArguments(bundle);
return MyFragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.my_fragment, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
instantiateWebView(view);
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
mWebView.restoreState(savedInstanceState);
} else {
Bundle args = getArguments();
if (args == null) {
return;
}
mUrl = args.getString("URL");
mPostData = args.getString("POSTDATA");
mWebView.postUrl("https://fakeurls.org/", EncodingUtils.getBytes(mPostData, "BASE64"));
}
}
#Override
public void onSaveInstanceState(final Bundle outState) {
super.onSaveInstanceState(outState);
if (mWebView != null) {
mWebView.saveState(outState);
}
}
#Override
public void onDestroy() {
super.onDestroy();
mWebView.destroy();
}
private void instantiateWebView(View view) {
mWebView = (WebView) view.findViewById(R.id.webView);
mWebView.setWebViewClient(new WebViewClient());
mWebView.getSettings().setJavaScriptEnabled(true);
}
}
MyActivity.java
public class MyActivity extends AppCompatActivity {
private MyFragment mMyFragment;
#NonNull
public static Intent createIntent(Context context, String url, String postData) {
return new Intent(context, MyActivity.class).putExtra("URL", url)
.putExtra("POSTDATA", postData);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_link_viewer);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowTitleEnabled(false);
mMyFragment = (MyFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_frame);
if (mMyFragment == null) {
final String url = getIntent().getStringExtra("URL");
final String postData = getIntent().getStringExtra("POSTDATA");
mMyFragment = MyFragment.newInstance(url, postData);
getSupportFragmentManager()
.beginTransaction()
.add(R.id.fragment_frame, mMyFragment)
.commitNow();
}
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
#Override
public void finish() {
mMyFragment.cancelTasks();
super.finish();
}
}
Please let me know how to resolve the issue and not reload the webview but retain display on orientation change. Any help is appreciated.
Related
I wrote this code for login and for chatting;
login is an activity where is chatting is a fragment; here is my activity :
public class Consultant extends AppCompatActivity {
private FirebaseAuth auth;
private Button loginbtn;
private EditText email;
private EditText pass;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_consultant);
auth = FirebaseAuth.getInstance();
loginbtn = (Button) findViewById(R.id.loginbtn);
email = (EditText) findViewById(R.id.email);
pass = (EditText) findViewById(R.id.pass);
loginbtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
checkEmailandpassword();
}
});
}
public boolean checkEmailandpassword() {
if (validate()) {
String CEmail = email.getText().toString().trim();
String CPass = pass.getText().toString().trim();
auth.signInWithEmailAndPassword(CEmail, CPass).addOnCompleteListener(new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
Toast.makeText(Consultant.this, "welcome", Toast.LENGTH_LONG).show();
Intent i = new Intent(Consultant.this,Consultant_Home_Chatting.class);
startActivity(i);
} else {
Toast.makeText(Consultant.this, "wrong ", Toast.LENGTH_LONG).show();
}
}
});
}
return false;
}
private boolean validate() {
boolean result = false;
String CPass = pass.getText().toString();
String CEmail = email.getText().toString();
if (CPass.isEmpty() || CEmail.isEmpty()) {
Toast.makeText(Consultant.this, "all fields required", Toast.LENGTH_LONG).show();
} else {
result = true;
}
return result;
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_nav, menu);
return true;
}
}
and here my fragment :
public class Consultant_Home_Chatting extends Fragment {
private ViewPager mViewP ;
private FirebaseAuth mAuth ;
private TabLayout mTab ;
private TabsPagerAdapter mAdapter ;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_consultant__home__chatting, container, false);
}
public Consultant_Home_Chatting() {
}
#Override
public void onViewCreated(#NonNull View view, #Nullable Bundle savedInstanceState) {
mAuth = FirebaseAuth.getInstance();
mViewP = (ViewPager) getView().findViewById(R.id.main_tabs_pager);
mAdapter = new TabsPagerAdapter(getFragmentManager());
mViewP.setAdapter(mAdapter);
mTab = (TabLayout) getView().findViewById(R.id.main_tabs);
mTab.setupWithViewPager(mViewP);
}
I tried to add a new class holding these liens then make a constructor in my fragment and onCreatOptions method in my activity but it does not work!
I think the solution is to use the Bundle but I don't know how to use it or can I use it and what can I send inside put extra, can you please help?
if (savedInstanceState == null){
getFragmentManager().beginTransaction()
.add(android.R.id.content, new Consultant_Home_Chatting ()).commit();}
to use bundle simple create the instance of your fragment and then create bundle add all data you want and add this bundle to the fragment.
Here is example:
In activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TestFragment testFragment = new TestFragment();
Bundle bundle = new Bundle();
bundle.putString("name", "John");
bundle.putInt("age", 24);
testFragment.setArguments(bundle);
getSupportFragmentManager()
.beginTransaction()
.add(R.id.content, testFragment).commit();
}
In your fragment:
public class TestFragment extends Fragment {
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle bundle = getArguments();
if(bundle != null){
String name = bundle.getString("name");
int age =`enter code here` bundle.getInt("age");
}
}
}
I have two tabs which contain two different fragments. Tab1 for fragment1 and tab2 for fragment2. From fragment1, I want to pass a string value in fragment2's TextView by clicking on a Button.
However, the UI of fragment2 is not being updated. Sample code has given below.
In MainActivity :
private Fragment1 fragment1;
private Fragment2 fragment2;
private TabLayout tabLayout;
protected void onCreate(Bundle savedInstanceState) {
fragment1 = new Fragment1();
fragment2 = new Fragment2();
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_holder, fragment1).commit();
tabLayout = (TabLayout) findViewById(R.id.tab);
tabLayout.getTabAt(0).select();
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
#Override
public void onTabSelected(TabLayout.Tab tab) {
if (tab.getPosition() == 0) {
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_holder, fragment1).commit();
} else if (tab.getPosition() == 1) {
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_holder, fragment2).commit();
}
}
#Override
public void onTabUnselected(TabLayout.Tab tab) {
}
#Override
public void onTabReselected(TabLayout.Tab tab) {
}
}
}
public void showFragment2(final String data) {
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_holder, fragment2).commit();
tabLayout.getTabAt(1).select();
if (!TextUtils.isEmpty(data)) {
if (fragment2 != null) {
fragment2.setData(data);
}
}
}
In Fragment1 :
private Button button;
protected void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
button = (Button) view.findViewById(R.id.btn);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
((MainActivity)getActivity()).showFragment2("Updated Data");
}
});
}
In Fragment2 :
private TextView tv;
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
tv = (TextView) view.findViewById(R.id.tv);
}
public void setData(String data){
tv.setText(data);
}
Important Update:
If I update the UI in Handler. Then it works. But, seems it's not the proper way.
handler.postDelayed(new Runnable() {
#Override
public void run() {
tabLayout.getTabAt(1).select();
if(!TextUtils.isEmpty(data)){
if(fragment2 != null){
fragment2.setData(data);
}
}
}
},500);
Use .executePendingTransactions() which would work synchronously along with .commit() which works Asynchronously.
After a FragmentTransaction is committed with
FragmentTransaction.commit(), it is scheduled to be executed
asynchronously on the process's main thread. If you want to
immediately executing any such pending operations, you can call this
function (only from the main thread) to do so. Note that all callbacks
and other related behavior will be done from within this call, so be
careful about where this is called from.
DO:
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_holder, fragment2).commit();
getSupportFragmentManager().executePendingTransactions();
tabLayout.getTabAt(1).select();
if(!TextUtils.isEmpty(data)){
if(fragment2 != null){
fragment2.setData(data);
}
}
Hey hi i am not sure but try this:
public void showFragment2(final String data) {
if (!TextUtils.isEmpty(data)) {
if (fragment2 != null) {
// fragment2.setData(data);
Bundle arguments = new Bundle();
arguments.putString( "string_key" , data);
fragment2.setArguments(arguments);
}
}
getSupportFragmentManager().beginTransaction().replace(R.id.fragment_holder, fragment2).commit();
tabLayout.getTabAt(1).select();
}
and In Fragment2 :
private TextView tv;
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Bundle arguments = getArguments();
String desired_string = arguments.getString("string_key");
tv = (TextView) view.findViewById(R.id.tv);
setData(desired_string);
}
public void setData(String data){
tv.setText(data);
}
set the data in activity method from fragment 1 and while loading fragment get the data from activity and set it in fragment 2
mainActivity(){
string fragment1Data;
public void setFragment1Data(string data){
fragment1Data = data;
}
public string getFragment1Data(){
return fragment1Data
}
}
in Fragment 1
private Button button;
protected void onViewCreated(View view, #Nullable Bundle savedInstanceState)
{
button = (Button) view.findViewById(R.id.btn);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
((MainActivity)getActivity()).showFragment2("Updated Data");
((MainActivity)getActivity()).setFragment1Data("data");
}
});
}
in Fragment 2
private TextView tv;
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
tv = (TextView) view.findViewById(R.id.tv);
tv.setText(((MainActivity)getActivity()).getFragment1Data());
}
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Fragment_view frag=new Fragment_view();
FragmentManager manager=getFragmentManager();
FragmentTransaction transaction=manager.beginTransaction();
transaction.add(R.id.fragment,frag,"hey");
transaction.commit();
}
#Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (inCustomView()) {
hideCustomView();
return true;
}
if ((mCustomView == null) && webView.canGoBack()) {
webView.goBack();
return true;
}
}
return super.onKeyDown(keyCode, event);
}
Now here variables like inCustomView and hidecustomView are defined in another fragment. So how to add this back button support on activity while using variables from a fragment. So I basically want to implement back button support in Android activity while using the defined variables from a fragment.
public class Fragment_view extends Fragment{
private WebView webView;
private FrameLayout customViewContainer;
private WebChromeClient.CustomViewCallback customViewCallback;
private View mCustomView;
private myWebChromeClient mWebChromeClient;
private myWebViewClient mWebViewClient;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// TODO Auto-generated method stub
View view= inflater.inflate(R.layout.fragment_view, container,false);
customViewContainer = (FrameLayout)view.findViewById(R.id.customViewContainer);
webView = (WebView)view. findViewById(R.id.webView);
mWebViewClient = new myWebViewClient();
webView.setWebViewClient(mWebViewClient);
mWebChromeClient = new myWebChromeClient();
webView.setWebChromeClient(mWebChromeClient);
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setAppCacheEnabled(true);
webView.getSettings().setBuiltInZoomControls(true);
webView.getSettings().setSaveFormData(true);
String htmlFile = "file:///" + Environment.getExternalStorageDirectory()
.getPath()
+ "/video_nsta/index.html";
webView.loadUrl(htmlFile);
return view;
}
public boolean inCustomView() {
return (mCustomView != null);
}
public void hideCustomView() {
mWebChromeClient.onHideCustomView();
}
#Override
public void onPause() {
super.onPause();
webView.onPause();
}
#Override
public void onResume() {
super.onResume();
webView.onResume();
}
#Override
public void onStop() {
super.onStop();
if (inCustomView()) {
hideCustomView();
}
}
class myWebChromeClient extends WebChromeClient {
#Override
public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) {
onShowCustomView(view, callback);
}
#Override
public void onShowCustomView(View view,CustomViewCallback callback) {
if (mCustomView != null) {
callback.onCustomViewHidden();
return;
}
mCustomView = view;
webView.setVisibility(View.GONE);
customViewContainer.setVisibility(View.VISIBLE);
customViewContainer.addView(view);
customViewCallback = callback;
}
#Override
public void onHideCustomView() {
super.onHideCustomView();
if (mCustomView == null)
return;
webView.setVisibility(View.VISIBLE);
customViewContainer.setVisibility(View.GONE);
mCustomView.setVisibility(View.GONE);
customViewContainer.removeView(mCustomView);
customViewCallback.onCustomViewHidden();
mCustomView = null;
}
}
class myWebViewClient extends WebViewClient {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
return super.shouldOverrideUrlLoading(view, url);
}
}
}
it's a matter of properly distributing the back button.
first create a fragment that can handle it:
public abstract class BackButtonFragment extends Fragment{
public boolean onBackPressed(){
return false; // defaults to false
}
}
then you change all your fragments to extend from BackButtonFragment.
Then on your activity you will use the appropriate callback for backbutton (remove the onKeyDown from your activity and use this instead):
#Override public void onBackPressed() {
Fragment f = getFragmentManager().findFragmentById(R.id.fragment);
if(f instanceof BackButtonFragment){
if(((BackButtonFragment)f).onBackPressed())
return;
}
// if fragment didn't use back button do the normal stuff
super.onBackPressed();
}
then inside each one of your fragments you will override the method onBackPressed and return the correct value true or false depending on your needs.
Ive been trying to do this for the best part of a week and still have no luck.
I have an action bar with tabs (action bar sherlock) and Im switching through the tabs and changing fragments fine.
Each fragment has a webview and Im trying to save the state of the webview so the web page stays on the same page that the user left it on.
Here is my fragment code:
public class FragmentB extends SherlockFragment {
WebView myWebView;
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
myWebView.saveState(outState);
Log.w("///////", "onSaveInstanceState");
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
myWebView.restoreState(savedInstanceState);
Log.w("///////", "onActivityCreated");
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.w("///////", "onCreate");
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View mainView = (View) inflater.inflate(R.layout.frag_b, container, false);
myWebView = (WebView) mainView.findViewById(R.id.webview);
Log.w("///////", "onCreateView");
if (savedInstanceState != null){
myWebView.restoreState(savedInstanceState);
Log.w("///////", "restore!");
}
else{
Log.w("///////", "ELSE!");
myWebView.setWebViewClient(new MyWebViewClient());
myWebView.getSettings().setPluginsEnabled(true);
myWebView.getSettings().setBuiltInZoomControls(false);
myWebView.getSettings().setSupportZoom(false);
myWebView.getSettings().setJavaScriptCanOpenWindowsAutomatically(true);
myWebView.getSettings().setAllowFileAccess(true);
myWebView.getSettings().setDomStorageEnabled(true);
myWebView.loadUrl("http://www.google.com");
}
//return inflater.inflate(R.layout.frag_b, container, false);
return mainView;
}
public class MyWebViewClient extends WebViewClient {
#Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.endsWith(".mp4"))
{
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(url), "video/*");
view.getContext().startActivity(intent);
return true;
}
else {
return super.shouldOverrideUrlLoading(view, url);
}
}
}
}
Any help would be great!
The onSaveInstanceState method of a fragment will only be called when the parent activity needs to save its state; an example of this is when the parent activity is being destroyed/recreated during an orientation change. As a result, this method won't be called when you are simply replacing fragments or switching between them as tabs.
As a workaround for your particular case, you can save the state of your webview in a bundle in the onPause method and restore it in the onActivityCreated method:
// Instance fields
private WebView webView;
private Bundle webViewBundle;
/**
* Save the state of the webview
*/
#Override
public void onPause()
{
super.onPause();
webViewBundle = new Bundle();
webView.saveState(webViewBundle);
}
/**
* Restore the state of the webview
*/
#Override
public void onActivityCreated(Bundle savedInstanceState)
{
super.onActivityCreated(savedInstanceState);
if (webViewBundle != null)
{
webView.restoreState(webViewBundle);
}
}
See here for more information as well:
How to save the state of a WebView inside a Fragment of an Action Bar
I have a Fragment activity that has a ListFragment on the left and a Fragment on the right that has a WebView. The functionality works fine but I would like to display a progress dialog showing "Loading..." while the web page finishes loading. How can I accomplish this seemingly trivial task?
Below is my code:
public class ArticleListActivity extends FragmentActivity implements
ArticleListFragment.OnArticleSelectedListener {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.articlelist_fragment);
}
public void onArticleSelected(String contentLink) {
ArticleViewerFragment viewer = (ArticleViewerFragment) getSupportFragmentManager()
.findFragmentById(R.id.articleview_fragment);
if (viewer == null || !viewer.isInLayout()) {
Intent showContent = new Intent(getApplicationContext(),
ArticleViewerActivity.class);
showContent.setData(Uri.parse(contentLink));
startActivity(showContent);
} else {
viewer.updateUrl(contentLink);
}
}
}
Here is my FragmentActivity
public class ArticleViewerActivity extends FragmentActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.articleview_fragment);
Intent launchingIntent = getIntent();
String content = launchingIntent.getData().toString();
ArticleViewerFragment viewer = (ArticleViewerFragment) getSupportFragmentManager()
.findFragmentById(R.id.articleview_fragment);
viewer.updateUrl(content);
}
}
and here is my Fragment
public class ArticleViewerFragment extends Fragment {
private ProgressDialog progressBar;
private WebView viewer = null;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
viewer = (WebView) inflater.inflate(R.layout.webview_layout, container, false);
viewer.loadUrl("file:///android_asset/default.html");
return viewer;
}
public void updateUrl(String newUrl) {
if (viewer != null) {
viewer.loadUrl(newUrl);
}
}
}
Use this code:
final ProgressDialog pd = ProgressDialog.show(this, "", "Loading...", true);
pd.setCancelable(false);
webview.getSettings().setJavaScriptEnabled(true);
webview.getSettings().setSupportZoom(true);
webview.getSettings().setBuiltInZoomControls(true);
webview.setWebViewClient(new WebViewClient() {
#Override
public void onPageFinished(WebView view, String url) {
if (pd != null && pd.isShowing()) {
pd.dismiss();
}
}
});
webview.loadUrl("file:///android_asset/default.html");