How to read PDF files in android

    0
    312

    Hello! In this tutorial we will learn how to read PDF files in android. Firstly, I would like to mention some important points. Till, Android 4.4 i.e. Kitkat, we had no option of viewing the PDF files other than viewing them using 3rd party-viewer like Google Drive viewer or other native PDF viewer in phone. Several libraries are available such as Android PDF viewer.

    Finally, there came PDFRenderer APIs, introduced in android 5.0, Lollipop version.  This tutorial explains how to use PdfRenderer to read PDF files in an android device.

    Output-

    Download Project-

    Download apk-

    So, let’s start.

    Creating a new project- PdfReader:

    Open Android Studio and create a new project. Name your project whatever you like. Be sure, to use the Minimum SDK as API 21(Android Lollipop). Choose an empty activity and click finish.

    We will proceed step by step:

    1.  Showing list of PDF files from the phone memory.
    2.  Creating activity for showing fragment. Fragment is added on the activity’s Frame  layout(acting as container). Here, we add permission checks for Android Marshmallow and higher versions.
    3.  Adding PDF rendering code in fragment.

    If you are a beginner you can learn the basics of android

    1. Showing list of pdf files

    Here, we create MainActivity, that will list all the PDF files from phone memory. First, look at the layout file for the MainActivity. That only consists of list view.

    activity_main.xml-

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout 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:orientation="vertical"
        android:layout_margin="10dp"
        tools:context="com.example.mitaly.pdfreader.MainActivity">
    
       <ListView
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:id="@+id/listView" />
    
    </LinearLayout>
    

     

    list_item.xml-

    This shows the code for list item to be used in list view. For this, right click layout folder in res -> New -> Layout Resource file. Name the file according to your choice. Click OK.

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    
        <TextView
            android:layout_height="wrap_content"
            android:layout_width="match_parent"
            android:textSize="14dp"
            android:id="@+id/txtFileName"
            android:padding="10dp"/>
    
    </LinearLayout>

     

    Bean class ‘FileBean.java’-

    We need to create a Bean/POJO class that has two attributes- fileName(name of the PDF file), filePath(absolute path of PDF file). Generate the getters and setters for those attributes and also a constructor with fields.

    public class FileBean {
    
        String fileName;
        String filePath;
    
        public FileBean(String fileName, String filePath) {
            this.fileName = fileName;
            this.filePath = filePath;
        }
    
        public String getFileName() {
            return fileName;
        }
    
        public void setFileName(String fileName) {
            this.fileName = fileName;
        }
    
        public String getFilePath() {
            return filePath;
        }
    
        public void setFilePath(String filePath) {
            this.filePath = filePath;
        }
    }
    

     

    Displaying list using Adapter class ‘FileAdapter.java’-

    Adapter plays the main role in populating the list view. Constructor takes 3 parameters as input- Context of activity, resource id of list_item we created earlier and the ArrayList of references of FileBean objects. ArrayList is initialized in the MainActivity, we will see that later. getView method takes the data from array list one by one and binds the data to list_item.

    public class FileAdapter extends ArrayAdapter<FileBean> {
        Context cxt;
        int res;
        ArrayList<FileBean> list;
    
        public FileAdapter(@NonNull Context context, @LayoutRes int resource, @NonNull ArrayList<FileBean> objects) {
            super(context, resource, objects);
    
            cxt = context;
            res = resource;
            list = objects;
    
        }
    
        @NonNull
        @Override
        public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
            //Initializing view which will point to layout file list_item
            View view = LayoutInflater.from(cxt).inflate(res, parent, false);
    
            //Text view showing pdf file name
            TextView txtView = (TextView)view.findViewById(R.id.txtFileName);
    
            //setting the file name
            txtView.setText(list.get(position).getFileName());
            return view;
        }
    }

     

    MainActivity.java-

    This is the activity where list will be shown. Here, we will discuss two important things-

    • Initialization of array list- In this code, we have a method initList that takes a single parameter i.e. absolute path of file. initList is called recursively for directory.
    • Permission checks-  The latest android versions(Android Marshmallow and higher) allows the user to install applications without granting permissions. checkPermission and onRequestPermissionResult are used for checking the permissions and handling the results.
    public class MainActivity extends AppCompatActivity {
    
        ListView listView;
        String path;
        ArrayList<FileBean> list;
        FileAdapter adapter;
    
        void initViews(){
            //views initialization
            listView = (ListView)findViewById(R.id.listView);
            list = new ArrayList<>();
    
            //get the absolute path of phone storage
            path = Environment.getExternalStorageDirectory().getAbsolutePath();
    
            //calling the initList that will initialize the list to be given to Adapter for binding data
            initList(path);
    
            adapter = new FileAdapter(this, R.layout.list_item, list);
    
            //set the adapter on listView
            listView.setAdapter(adapter);
    
            //when user chooses a particular pdf file from list,
            //start another activity that will show the pdf
            listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                @Override
                public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                    Intent intent = new Intent(MainActivity.this, PdfViewerActivity.class);
                    intent.putExtra("keyName", list.get(position).getFileName());
                    intent.putExtra("keyPath",list.get(position).getFilePath());
                    startActivity(intent);
                }
            });
        }
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            setTitle("Pdf Reader");
            //Check if permission is granted(for Marshmallow and higher versions)
            if (Build.VERSION.SDK_INT >= 23)
                checkPermission();
            else
                initViews();
        }
    
        //initializing the ArrayList
        void initList(String path){
            try{
                File file = new File(path);
                File[] fileArr = file.listFiles();
                String fileName;
                for(File file1 : fileArr){
                    if(file1.isDirectory()){
                        initList(file1.getAbsolutePath());
                    }else{
                        fileName = file1.getName();
                        //choose only the pdf files
                        if(fileName.endsWith(".pdf")){
                            list.add(new FileBean(fileName, file1.getAbsolutePath()));
                        }
                    }
    
                }
            }catch(Exception e){
                Log.i("show","Something went wrong. "+e.toString());
            }
        }
    
        //Handling permissions for Android Marshmallow and above
        void checkPermission() {
            if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
                //if permission granted, initialize the views
                initViews();
            } else {
                //show the dialog requesting to grant permission
                ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
            }
        }
    
        @Override
        public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    
            switch (requestCode) {
                case 1:
                    if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                        initViews();
                    } else {
                        //permission is denied (this is the first time, when "never ask again" is not checked)
                        if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_EXTERNAL_STORAGE)) {
                            finish();
                        }
                        //permission is denied (and never ask again is  checked)
                        else {
                            //shows the dialog describing the importance of permission, so that user should grant
                            AlertDialog.Builder builder = new AlertDialog.Builder(this);
                            builder.setMessage("You have forcefully denied Read storage permission.\n\nThis is necessary for the working of app." + "\n\n" + "Click on 'Grant' to grant permission")
                                    //This will open app information where user can manually grant requested permission
                                    .setPositiveButton("Grant", new DialogInterface.OnClickListener() {
                                        public void onClick(DialogInterface dialog, int id) {
                                            finish();
                                            Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
                                                    Uri.fromParts("package", getPackageName(), null));
                                            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                                            startActivity(intent);
                                        }
                                    })
                                    //close the app
                                    .setNegativeButton("Don't", new DialogInterface.OnClickListener() {
                                        public void onClick(DialogInterface dialog, int id) {
                                            finish();
                                        }
                                    });
                            builder.setCancelable(false);
                            builder.create().show();
                        }
                    }
            }
        }
    }

    Related:

    Designing Custom tableView Cell or Designing an article reading app

    Read sms from Inbox and display in RecyclerView

    Read Text using Mobile Vision Text Recognization API in android

    Android QR barcode Reader Using Mobile vision API

    Uploading file to Google Drive in Android Application

    2. Creating activity for showing fragment

    Here, we will first create an activity ‘PdfViewerActivity’ and then add the fragment to this activity. The fragment can be added to activity in two ways- static way and dynamic way. We will use dynamic approach.

    activity_pdf_viewer.xml-

    As you can see, we have only frame layout in activity’s layout file. We will see later, that how fragment can be embedded in this activity.

    <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout 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:orientation="vertical"
        android:id="@+id/frame"
        tools:context="com.example.mitaly.pdfreader.PdfViewerActivity">
    
    </FrameLayout>
    

     

    PdfViewerActivity.java-

    Firstly, we will get the variables- file name and file path(for the chosen file in list view) from the previous activity. We need to transfer these values to fragment. So, we used Bundle for storing the data. setArguments will supply the data to specified fragment and at the other end(fragment) data will be retrieved using getArguments.

    Now, let’s understand how fragment is added in activity.

    getSupportFragmentManager() will return FragmentManager which will manage the fragment and or you can say handing the interaction with fragment.

    beginTransaction() starts a series of edit operations(add, replace, remove) on the fragments.

    add method add the specified fragment to the activity state. We will pass the id of frame layout of activity on which fragment is added.

    commit() schedules this transaction

    public class PdfViewerActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_pdf_viewer);
    
            Intent intent = getIntent();
            String name = intent.getStringExtra("keyName");
            String path = intent.getStringExtra("keyPath");
    
            setTitle(name);
    
            PdfFragment fragment = new PdfFragment();
            Bundle bundle = new Bundle();
            bundle.putString("keyPath", path);
            fragment.setArguments(bundle);
    
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.frame, fragment, "Pdf renderer")
                    .commit();
        }
    }
    

     

    3. Adding PDF rendering code in fragment

    fragment_pdf.xml-

    We used an image view which will display the pdf file. There are two buttons- previous and next that will open the previous and next page respective to current page. For changing the color of text on button, we created a drawable resource file(we will see that next) and set that as textColor in buttons.

    <?xml version="1.0" encoding="utf-8"?>
    <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="vertical"
            tools:context="com.example.mitaly.pdfreader.PdfFragment">
    
            <ImageView
                android:id="@+id/imgView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_weight="1"
                android:scaleType="fitCenter"
                android:background="@android:color/white" />
    
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal">
    
                <Button
                    android:id="@+id/btnPrevious"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:background="@android:color/white"
                    android:layout_weight="1"
                    android:textColor="@drawable/btn_txt_color"
                    android:text="Previous" />
    
                <Button
                    android:id="@+id/btnNext"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:background="@android:color/white"
                    android:layout_weight="1"
                    android:textColor="@drawable/btn_txt_color"
                    android:text="Next" />
            </LinearLayout>
    
        </LinearLayout>
    

     

    btn_txt_color.xml-

    Create drawable resource file and for state enabled and disabled for the button, change the color of text.

    <?xml version="1.0" encoding="utf-8"?>
    <selector xmlns:android="http://schemas.android.com/apk/res/android">
    
        <item android:state_enabled="true" android:color="@color/colorPrimary"/>
        <item android:state_enabled="false" android:color="@color/btn_disabled"/>
    
    </selector>

     

    PdfFragment.java-

    Here, we use PdfRenderer that enables rendering a PDF document. Firstly you create a renderer and for every page you want to render, you open the page, render it, and close the page.

    When fragment is started, openPdfRenderer is called. There, we first initialize ParcelFileDescriptor by calling open method. This will open a file with a specific name and return a file descriptor. Then, initialize the PdfRenderer by passing parcelFileDescriptor as argument. Then displayPage method is called. Let’s understand this method.

    displayPage():

    createBitmap returns a mutable bitmap(whose pixels can be changed) with the specified width and height. Third argument which goes to this function is Bitmap.Config that describes how pixels are stored. AGRB_8888 tells that each pixel is stored on 4 bytes.

    render method renders page to a bitmap. In this method, we first specify destination bitmap as first parameter and null for next two parameters. Last parameter is RENDER_MODE_FOR_DISPLAY as our purpose is just to display the page.

    After that, enable or disable the buttons depending on whether the page opened is last or first.

    Make sure, to close the page, pdfRenderer and parcelFileDescriptor when fragment is stopped.

    Other methods are self understandable.

    public class PdfFragment extends Fragment implements View.OnClickListener{
    
        String path;
        ImageView imgView;
        Button btnPrevious, btnNext;
        int pageIndex;
        PdfRenderer pdfRenderer;
        PdfRenderer.Page curPage;
        ParcelFileDescriptor descriptor;
    
        public PdfFragment() {}
    
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
        }
    
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                                 Bundle savedInstanceState) {
            //get the path of file
            path = getArguments().getString("keyPath");
            return inflater.inflate(R.layout.fragment_pdf, container, false);
        }
    
        @Override
        public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
            super.onViewCreated(view, savedInstanceState);
            
            //initializing the views
            imgView = (ImageView)view.findViewById(R.id.imgView);
            btnPrevious = (Button)view.findViewById(R.id.btnPrevious);
            btnNext = (Button)view.findViewById(R.id.btnNext);
            //set click listener on buttons
            btnPrevious.setOnClickListener(this);
            btnNext.setOnClickListener(this);
        }
    
        @Override
        public void onStart() {
            super.onStart();
            try {
                openPdfRenderer();
                displayPage(pageIndex);
            } catch (Exception e) {
                Toast.makeText(getActivity(), "Sorry! This pdf is protected with password.", Toast.LENGTH_SHORT).show();
            }
        }
    
        @Override
        public void onStop() {
            try {
                closePdfRenderer();
            } catch (IOException e) {
                e.printStackTrace();
            }
            super.onStop();
        }
    
    
        void openPdfRenderer(){
            File file = new File(path);
            descriptor = null;
            pdfRenderer = null;
             try {
                descriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
                pdfRenderer = new PdfRenderer(descriptor);
            } catch (Exception e) {
                Toast.makeText(getContext(), "There's some error", Toast.LENGTH_LONG).show();
            }
    
        }
        
        private void closePdfRenderer() throws IOException {
            if (curPage != null)
                curPage.close();
            if (pdfRenderer != null)
                 pdfRenderer.close();
            if(descriptor !=null)
                descriptor.close();
        }
        
        void displayPage(int index){
            if(pdfRenderer.getPageCount() <= index)
                return;
            //close the current page
            if(curPage != null)
                curPage.close();
            //open the specified page
            curPage = pdfRenderer.openPage(index);
            //get page width in points(1/72")
            int pageWidth = curPage.getWidth();
            //get page height in points(1/72")
            int pageHeight = curPage.getHeight();
            //returns a mutable bitmap
            Bitmap bitmap = Bitmap.createBitmap(pageWidth, pageHeight, Bitmap.Config.ARGB_8888);
            //render the page on bitmap
            curPage.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
            //display the bitmap
            imgView.setImageBitmap(bitmap);
            //enable or disable the button accordingly
            int pageCount = pdfRenderer.getPageCount();
            btnPrevious.setEnabled(0 != index);
            btnNext.setEnabled(index + 1 < pageCount);
     }
    
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.btnPrevious: {
                    //get the index of previous page
                    int index = curPage.getIndex()-1;
                    displayPage(index);
                    break;
                }
                case R.id.btnNext: {
                    //get the index of previous page
                    int index = curPage.getIndex()+1;
                    displayPage(index);
                    break;
                }
            }
        }
    }

    Have problem developing the app? you can learn android as a beginner.

    Conclusion-

    In this example, we saw how we can use PdfRenderer for showing the pdf files in android.  Hope you understood. In case of any queries, you may ask questions. Keep following more amazing Android Blogs.


    If you really liked the article, please subscribe to our YouTube Channel for videos related to this article.Please find us on Twitter and Facebook.

    LEAVE A REPLY

    Please enter your comment!
    Please enter your name here