What is alternative for "android:usesCleartextTraffic"  in android API LEVEL 29? 
Tags: android, java
Published: 2020-05-04 06:15:18

I'm developing wordpress android app which loads Wordpress post data in android webview. I'm using `retrofit & JSON API` to fetch data from my Wordpress site. My app worked fine in android API LEVEL 26 but the problem is I need API 28+ to upload to google play store. I migrated to androidx API LEVEL 29. post were not getting loaded showing `ERR_CLEARTEXT_NOT_PERMITTED` so I used `"android:usesCleartextTraffic=true"` post was loading but the problem is HTML entities like `&#8211,&#8220,&#8217` etc failed to decode and decoded as & and remaining lines of the post are not loaded after this. I dint use `"android:usesCleartextTraffic=true"` in API LEVEL 26 but it works fine without any error. I'm searching a solution for my problem I didn't find satisfying answers anywhere. I'm stuck at this point. So I'm trying alternative method for `"android:usesCleartextTraffic=true"`. I have "text to speech" function in my app its working absolutely fine I mean its reading entire post which is not visible in android webview. Kindly suggest. Please find the code for your reference.

[WEBVIEW SCREESHOT][1]


  [1]: https://i.stack.imgur.com/CF6Ru.jpg
this is my postdetails activity

**Postdetails.java**

    package ak.wp.meto.activity;
    
    import android.app.Activity;
    import android.content.Context;
    import android.content.Intent;
    import android.os.Bundle;
    import com.google.android.material.floatingactionbutton.FloatingActionButton;
    import androidx.fragment.app.FragmentManager;
    import androidx.core.content.ContextCompat;
    import androidx.recyclerview.widget.LinearLayoutManager;
    import androidx.recyclerview.widget.RecyclerView;
    import android.text.Html;
    import android.view.MenuItem;
    import android.view.View;
    import android.webkit.WebView;
    import android.widget.ImageButton;
    import android.widget.ImageView;
    import android.widget.RelativeLayout;
    import android.widget.TextView;
    
    import com.bumptech.glide.Glide;
    import ak.wp.meto.R;
    import ak.wp.meto.adapters.CommentsAdapter;
    import ak.wp.meto.api.http.ApiUtils;
    import ak.wp.meto.api.models.posts.post.CommentsAndReplies;
    import ak.wp.meto.api.models.posts.post.PostDetails;
    import ak.wp.meto.api.params.HttpParams;
    import ak.wp.meto.data.constant.AppConstant;
    import ak.wp.meto.data.sqlite.FavouriteDbController;
    import ak.wp.meto.fragment.WriteACommentFragment;
    import ak.wp.meto.listeners.ListItemClickListener;
    import ak.wp.meto.models.FavouriteModel;
    import ak.wp.meto.utility.ActivityUtils;
    import ak.wp.meto.utility.AppUtils;
    import ak.wp.meto.utility.TtsEngine;
    import ak.wp.meto.webengine.WebEngine;
    import ak.wp.meto.webengine.WebListener;
    
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.List;
    
    import retrofit2.Call;
    import retrofit2.Callback;
    import retrofit2.Response;
    
    public class PostDetailsActivity extends BaseActivity implements WriteACommentFragment.OnCompleteListener {
    
        // Variables
        private Activity mActivity;
        private Context mContext;
    
        // init views
        private RelativeLayout lytPostDetailsView, lytCommentDetails;
        private int clickedPostId;
        private ImageView imgPost;
        private TextView tvPostTitle, tvPostAuthor, tvPostDate, tvCommnentList;
        private WebView webView;
        private FloatingActionButton fabWriteAComment;
        private ImageButton imgBtnSpeaker, imgBtnFav, imgBtnShare;
        private PostDetails model = null;
    
        // Favourites view
        private List<FavouriteModel> favouriteList;
        private FavouriteDbController favouriteDbController;
        private boolean isFavourite = false;
    
        // Comments view
        private List<CommentsAndReplies> commentList;
        private List<CommentsAndReplies> zeroParentComments;
        List<CommentsAndReplies> onlyThreeComments;
        private int mPerPage = 5;
        private RecyclerView rvComments;
        private CommentsAdapter commentsAdapter = null;
    
        // Text to speech
        private TtsEngine ttsEngine;
        private boolean isTtsPlaying = false;
        private String ttsText;
    
        private WebEngine webEngine;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            initVar();
            initView();
            initFunctionality();
            initListener();
        }
    
        private void initVar() {
            mActivity = PostDetailsActivity.this;
            mContext = mActivity.getApplicationContext();
    
            // Favourites view
            favouriteList = new ArrayList<>();
    
            // Comments view
            commentList = new ArrayList<>();
            zeroParentComments = new ArrayList<>();
            onlyThreeComments = new ArrayList<>();
    
            Intent intent = getIntent();
            if (intent != null) {
                clickedPostId = getIntent().getIntExtra(AppConstant.BUNDLE_KEY_POST_ID, 0);
            }
        }
    
        private void initView() {
            setContentView(R.layout.activity_post_details);
    
            lytPostDetailsView = (RelativeLayout) findViewById(R.id.lyt_post_details);
            lytCommentDetails = (RelativeLayout) findViewById(R.id.lyt_comment_list);
            //lytParentView.setVisibility(View.GONE);
    
            imgPost = (ImageView) findViewById(R.id.post_img);
            tvPostTitle = (TextView) findViewById(R.id.title_text);
            tvPostAuthor = (TextView) findViewById(R.id.post_author);
            tvPostDate = (TextView) findViewById(R.id.date_text);
            imgBtnSpeaker = (ImageButton) findViewById(R.id.imgBtnSpeaker);
            imgBtnFav = (ImageButton) findViewById(R.id.imgBtnFavourite);
            imgBtnShare = (ImageButton) findViewById(R.id.imgBtnShare);
    
            initWebEngine();
    
    
            tvCommnentList = (TextView) findViewById(R.id.comment_count);
            fabWriteAComment = (FloatingActionButton) findViewById(R.id.fab_new_comment);
    
            rvComments = (RecyclerView) findViewById(R.id.rvComments);
            rvComments.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
    
            initLoader();
    
            initToolbar();
            enableBackButton();
    
        }
    
        public void initWebEngine() {
    
            webView = (WebView) findViewById(R.id.web_view);
            webEngine = new WebEngine(webView, mActivity);
            webEngine.initWebView();
            webEngine.initListeners(new WebListener() {
                @Override
                public void onStart() {
                    initLoader();
                }
    
                @Override
                public void onLoaded() {
                    hideLoader();
                }
    
                @Override
                public void onProgress(int progress) {
                }
    
                @Override
                public void onNetworkError() {
                    showEmptyView();
                }
    
                @Override
                public void onPageTitle(String title) {
                }
            });
        }
    
        private void initFunctionality() {
    
            favouriteDbController = new FavouriteDbController(mContext);
            favouriteList.addAll(favouriteDbController.getAllData());
            for (int i = 0; i < favouriteList.size(); i++) {
                if (favouriteList.get(i).getPostId() == clickedPostId) {
                    isFavourite = true;
                    break;
                }
            }
    
            ttsEngine = new TtsEngine(mActivity);
    
            commentsAdapter = new CommentsAdapter(mActivity, (ArrayList) commentList, (ArrayList) onlyThreeComments);
            rvComments.setAdapter(commentsAdapter);
    
            showLoader();
            loadPostDetails();
    
            // show full-screen ads
            // AdUtils.getInstance(mContext).showFullScreenAd();
    
        }
    
        public void setFavImage() {
            if (isFavourite) {
                imgBtnFav.setImageDrawable(ContextCompat.getDrawable(mActivity, R.drawable.ic_book));
            } else {
                imgBtnFav.setImageDrawable(ContextCompat.getDrawable(mActivity, R.drawable.ic_un_book));
            }
        }
    
        public void initListener() {
    
            imgBtnSpeaker.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (model != null) {
                        toggleTtsPlay();
                    }
                }
            });
    
            imgBtnFav.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (model != null) {
                        isFavourite = !isFavourite;
                        if (isFavourite) {
                            String imgUrl = null;
                            if (model.getEmbedded().getWpFeaturedMedias().size() >= 1) {
                                imgUrl = model.getEmbedded().getWpFeaturedMedias().get(0).getMediaDetails().getSizes().getFullSize().getSourceUrl();
                            }
                            favouriteDbController.insertData(
                                    model.getID().intValue(),
                                    imgUrl,
                                    model.getTitle().getRendered(),
                                    model.getOldDate(),
                                    model.getEmbedded().getWpTerms().get(0).get(0).getName()
                            );
                        } else {
                            favouriteDbController.deleteFav(clickedPostId);
                        }
                        setFavImage();
                    }
                }
            });
    
            imgBtnShare.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    if (model != null) {
                        Intent sendIntent = new Intent();
                        sendIntent.setAction(Intent.ACTION_SEND);
                        sendIntent.putExtra(Intent.EXTRA_TEXT, model.getPageUrl());
                        sendIntent.setType("text/plain");
                        startActivity(Intent.createChooser(sendIntent, getResources().getText(R.string.send_to)));
                    }
                }
            });
    
            commentsAdapter.setItemClickListener(new ListItemClickListener() {
                @Override
                public void onItemClick(int position, View view) {
                    int id = view.getId();
                    CommentsAndReplies clickedComment = zeroParentComments.get(position);
                    switch (id) {
                        case R.id.list_item:
                            ActivityUtils.getInstance().invokeCommentDetails(mActivity, CommentDetailsActivity.class, (ArrayList) commentList, clickedPostId, clickedComment, false, false);
                            break;
                        case R.id.reply_text:
                            ActivityUtils.getInstance().invokeCommentDetails(mActivity, CommentDetailsActivity.class, (ArrayList) commentList, clickedPostId, clickedComment, true, false);
                            break;
                        default:
                            break;
                    }
                }
            });
    
            tvCommnentList.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    ActivityUtils.getInstance().invokeCommentList(mActivity,
                            CommentListActivity.class,
                            (ArrayList) commentList,
                            (ArrayList) zeroParentComments,
                            clickedPostId,
                            false);
                }
            });
    
            fabWriteAComment.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    FragmentManager manager = getSupportFragmentManager();
                    WriteACommentFragment dialog = WriteACommentFragment.newInstance(clickedPostId, AppConstant.THIS_IS_COMMENT);
                    dialog.show(manager, AppConstant.BUNDLE_KEY_DIALOG_FRAGMENT);
                }
            });
        }
    
        public void loadPostDetails() {
            ApiUtils.getApiInterface().getPostDetails(clickedPostId).enqueue(new Callback<PostDetails>() {
                @Override
                public void onResponse(Call<PostDetails> call, Response<PostDetails> response) {
                    if (response.isSuccessful()) {
                        // bind data
                        model = response.body();
                        PostDetails m = model;
                        // visible parent view
                        lytPostDetailsView.setVisibility(View.VISIBLE);
                        // visible comments button
                        fabWriteAComment.setVisibility(View.VISIBLE);
    
                        loadCommentsAndReplies(model.getLinks().getRepliesList().get(0).getHref());
    
                        setFavImage();
    
    
                        tvPostTitle.setText(Html.fromHtml(model.getTitle().getRendered()));
    
                        String imgUrl = null;
                        if (model.getEmbedded().getWpFeaturedMedias().size() > 0) {
                            if (model.getEmbedded().getWpFeaturedMedias().get(0).getMediaDetails() != null) {
                                if (model.getEmbedded().getWpFeaturedMedias().get(0).getMediaDetails().getSizes().getFullSize().getSourceUrl() != null) {
                                    imgUrl = model.getEmbedded().getWpFeaturedMedias().get(0).getMediaDetails().getSizes().getFullSize().getSourceUrl();
                                }
                            }
                        }
    
                        if (imgUrl != null) {
                            Glide.with(getApplicationContext())
                                    .load(imgUrl)
                                    .into(imgPost);
                        }
    
                        String author = null;
                        if (model.getEmbedded().getAuthors().size() >= 1) {
                            author = model.getEmbedded().getAuthors().get(0).getName();
                        }
    
                        if (author == null) {
                            author = getString(R.string.admin);
                        }
                        tvPostAuthor.setText(Html.fromHtml(author));
    
                        String oldDate = model.getOldDate();
                        String newDate = AppUtils.getFormattedDate(oldDate);
    
                        if (newDate != null) {
                            tvPostDate.setText(Html.fromHtml(newDate));
                        }
    
                        String contentText = model.getContent().getRendered();
    
                        ttsText = new StringBuilder(Html.fromHtml(model.getTitle().getRendered())).append(AppConstant.DOT).append(Html.fromHtml(model.getContent().getRendered())).toString();
                        webView.loadData(contentText, "text/html", "UTF-8"); //comment
                        contentText = new StringBuilder().append(AppConstant.CSS_PROPERTIES).append(contentText).toString();
                        webEngine.loadHtml(contentText);
    
                    } else {
                        showEmptyView();
                    }
                }
    
                @Override
                public void onFailure(Call<PostDetails> call, Throwable t) {
                    t.printStackTrace();
                    // hide common loader
                    hideLoader();
                    // show empty view
                    showEmptyView();
                }
            });
        }
    
        public void loadCommentsAndReplies(final String commentsAndRepliesLink) {
    
            ApiUtils.getApiInterface().getCommentsAndReplies(commentsAndRepliesLink, mPerPage).enqueue(new Callback<List<CommentsAndReplies>>() {
                @Override
                public void onResponse(Call<List<CommentsAndReplies>> call, Response<List<CommentsAndReplies>> response) {
                    if (response.isSuccessful()) {
    
                        int totalItems = Integer.parseInt(response.headers().get(HttpParams.HEADER_TOTAL_ITEM));
                        int totalPages = Integer.parseInt(response.headers().get(HttpParams.HEADER_TOTAL_PAGE));
    
    
                        if (totalPages > 1) {
                            mPerPage = mPerPage * totalPages;
                            loadCommentsAndReplies(commentsAndRepliesLink);
    
                        } else {
                            if (!commentList.isEmpty() || !zeroParentComments.isEmpty() || !onlyThreeComments.isEmpty()) {
                                commentList.clear();
                                zeroParentComments.clear();
                                onlyThreeComments.clear();
                            }
    
                            commentList.addAll(response.body());
    
                            lytCommentDetails.setVisibility(View.VISIBLE);
    
                            if (commentList.size() > 0) {
                                for (CommentsAndReplies commentsAndReplies : commentList) {
                                    if (commentsAndReplies.getParent().intValue() == 0) {
                                        zeroParentComments.add(commentsAndReplies);
                                    }
                                }
    
                                if (zeroParentComments.size() >= 3) {
                                    for (int i = 0; i < 3; i++) {
                                        onlyThreeComments.add(zeroParentComments.get(i));
                                    }
                                } else {
                                    for (CommentsAndReplies commentsAndReplies : zeroParentComments) {
                                        onlyThreeComments.add(commentsAndReplies);
                                    }
                                }
                                commentsAdapter.notifyDataSetChanged();
                                tvCommnentList.setText(String.format(getString(R.string.all_comment), commentList.size()));
                                tvCommnentList.setClickable(true);
    
                            } else {
                                tvCommnentList.setClickable(false);
                            }
                        }
    
                    }
                }
    
                @Override
                public void onFailure(Call<List<CommentsAndReplies>> call, Throwable t) {
                    showEmptyView();
                    t.printStackTrace();
                }
            });
        }
    
        private void toggleTtsPlay() {
            if (isTtsPlaying) {
                ttsEngine.releaseEngine();
                isTtsPlaying = false;
            } else {
                ttsEngine.startEngine(ttsText);
                isTtsPlaying = true;
            }
            toggleTtsView();
        }
    
        private void toggleTtsView() {
            if (isTtsPlaying) {
                imgBtnSpeaker.setImageDrawable(getResources().getDrawable(R.drawable.ic_speaker_stop));
            } else {
                imgBtnSpeaker.setImageDrawable(getResources().getDrawable(R.drawable.ic_speaker));
            }
        }
    
        @Override
        public boolean onOptionsItemSelected(MenuItem item) {
            switch (item.getItemId()) {
                // Respond to the action bar's Up/Home button
                case android.R.id.home:
                    finish();
                    return true;
            }
            return super.onOptionsItemSelected(item);
        }
    
        @Override
        public void onBackPressed() {
            finish();
        }
    
        @Override
        protected void onStop() {
            super.onStop();
            ttsEngine.releaseEngine();
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
            ttsEngine.releaseEngine();
            model = null;
        }
    
        @Override
        protected void onResume() {
            super.onResume();
            if (isTtsPlaying) {
                isTtsPlaying = false;
                imgBtnSpeaker.setImageDrawable(getResources().getDrawable(R.drawable.ic_speaker));
            }
        }
    
        @Override
        public void onComplete(Boolean isCommentSuccessful, CommentsAndReplies commentsAndReplies) {
            if (isCommentSuccessful) {
                loadCommentsAndReplies(model.getLinks().getRepliesList().get(0).getHref());
            }
        }
    
        @Override
        protected void onActivityResult(int requestCode, int resultCode, Intent data) {
            if (resultCode != RESULT_OK) {
                return;
            }
    
            if (requestCode == AppConstant.REQUEST_CODE_COMMENT) {
                if (data == null) {
                    return;
                }
                boolean isCommentSuccessful = CommentListActivity.wasCommentSuccessful(data);
                if (isCommentSuccessful) {
                    loadCommentsAndReplies(model.getLinks().getRepliesList().get(0).getHref());
                }
            }
        }
    }

**activity_post_details.xml**

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.coordinatorlayout.widget.CoordinatorLayout 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:background="@color/white"
        android:fitsSystemWindows="true">
    
        <com.google.android.material.appbar.AppBarLayout
            android:id="@+id/app_bar_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:fitsSystemWindows="true"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
    
            <com.google.android.material.appbar.CollapsingToolbarLayout
                android:id="@+id/collapsing_toolbar"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:fitsSystemWindows="true"
                app:expandedTitleMarginEnd="64dp"
                app:expandedTitleMarginStart="48dp"
                app:layout_scrollFlags="scroll|exitUntilCollapsed">
    
                <ImageView
                    android:id="@+id/post_img"
                    android:layout_width="match_parent"
                    android:layout_height="@dimen/margin_250dp"
                    android:fitsSystemWindows="true"
                    android:scaleType="centerCrop"
                    android:src="@color/white"
                    app:layout_collapseMode="parallax" />
    
                <androidx.appcompat.widget.Toolbar
                    android:id="@+id/toolbar"
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize"
                    app:layout_collapseMode="pin"
                    app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
    
                    <RelativeLayout
                        android:layout_width="match_parent"
                        android:layout_height="match_parent">
    
                        <ImageButton
                            android:id="@+id/imgBtnSpeaker"
                            android:layout_width="30dp"
                            android:layout_height="30dp"
                            android:layout_centerVertical="true"
                            android:layout_marginRight="10dp"
                            android:layout_toLeftOf="@id/imgBtnFavourite"
                            android:padding="5dp"
                            android:scaleType="centerInside"
                            android:src="@drawable/ic_speaker" />
                    </RelativeLayout>
    
                </androidx.appcompat.widget.Toolbar>
            </com.google.android.material.appbar.CollapsingToolbarLayout>
        </com.google.android.material.appbar.AppBarLayout>
    
        <androidx.core.widget.NestedScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">
    
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical">
                <include layout="@layout/content_post_details" />
                <include layout="@layout/content_comments" />
            </LinearLayout>
    
        </androidx.core.widget.NestedScrollView>
        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/fab_new_comment"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|end"
            android:layout_margin="@dimen/activity_vertical_margin"
            android:src="@drawable/ic_fab"
            android:visibility="gone" />
        <include layout="@layout/view_common_loader" />    </androidx.coordinatorlayout.widget.CoordinatorLayout>


**content_post_details.xml**

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/lyt_post_details"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/white"
        android:visibility="gone">
    
        <LinearLayout
            android:id="@+id/li_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="@dimen/margin_8dp"
            android:layout_marginTop="@dimen/margin_8dp"
            android:orientation="horizontal"
            android:weightSum="1">
    
            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="@dimen/margin_20dp"
                android:layout_weight="0.5"
                android:gravity="center_vertical"
                android:padding="@dimen/margin_8dp">
    
                <ImageView
                    android:id="@+id/author_image"
                    android:layout_width="@dimen/margin_30dp"
                    android:layout_height="@dimen/margin_30dp"
                    android:background="@drawable/ic_author" />    
            </LinearLayout>
    
            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="@dimen/margin_20dp"
                android:layout_weight="0.5"
                android:gravity="center_vertical"
                android:padding="@dimen/margin_8dp">
            </LinearLayout>
    
        </LinearLayout>
    
        <View
            android:id="@+id/viewDivider"
            android:layout_width="match_parent"
            android:layout_height="0.7dp"
            android:layout_below="@id/li_layout"
            android:layout_marginLeft="@dimen/margin_15dp"
            android:layout_marginRight="@dimen/margin_15dp"
            android:background="@color/toolbar_boarder" />
    
        <TextView
            android:id="@+id/title_text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/viewDivider"
            android:layout_margin="@dimen/margin_8dp"
            android:textColor="@color/black"
            android:textSize="18sp"
            android:textStyle="bold"
            tools:text="This is sample text do yoy know that it supports multi-line" />
    
        <WebView
            android:id="@+id/web_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/title_text"
            android:layout_margin="@dimen/margin_8dp"
            android:paddingBottom="@dimen/margin_8dp" />
    
    </RelativeLayout>