What is alternative for “android:usesCleartextTraffic” in android API LEVEL 29?

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

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>

原文链接:What is alternative for “android:usesCleartextTraffic” in android API LEVEL 29?

© 版权声明
THE END
喜欢就支持一下吧
点赞11 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容