WebViewCompat.navigate-এর মাধ্যমে উন্নত পৃষ্ঠা নেভিগেশন

WebViewCompat.navigate হল WebView.loadUrl-এর একটি উন্নত বিকল্প যা WebView-এ ওয়েব পেজ লোডিং, ইতিহাস ম্যানেজমেন্ট এবং নেভিগেশন লাইফসাইকেল ট্র্যাকিংয়ের উপর সূক্ষ্ম নিয়ন্ত্রণ প্রদান করে।

আগে, loadUrl ব্যবহার করে পেজ নেভিগেশন শুরু করার ক্ষেত্রে উল্লেখযোগ্য সীমাবদ্ধতা ছিল:

  • ইতিহাসের এন্ট্রি পরিবর্তন করা যাবে না: আপনি বর্তমান ইতিহাসের এন্ট্রি পরিবর্তন করতে পারবেন না, যার ফলে ব্যাক স্ট্যাকে এন্ট্রি যোগ না করে নতুন পৃষ্ঠায় নেভিগেট করা অসম্ভব হয়ে পড়ে।
  • ডিকাপল্ড কলব্যাক: WebViewClient-এ পরবর্তী কলব্যাক ইভেন্টের সাথে নির্দিষ্ট loadUrl কলের সম্পর্ক স্থাপন করার কোনও সরাসরি মেকানিজম ছিল না।
  • অতিরিক্ত হেডার সেভ করা হয়নি: loadUrl-এ পাস করা কাস্টম হেডার WebView স্টেট-এর অংশ হিসেবে সেভ করা হয়নি, তাই স্টেট রিস্টোর করার সময় সেগুলি হারিয়ে গেছে।

WebViewCompat.navigate API নিম্নলিখিত ফিচারগুলি নিয়ে আসার মাধ্যমে এইসব সমস্যার সমাধান করে:

  • নেভিগেশন ইতিহাস এন্ট্রি রিপ্লেসমেন্ট: WebView ইতিহাস স্ট্যাকে বর্তমান পৃষ্ঠা রিপ্লেস করতে দেয়।
  • সম্পর্কিত কলব্যাক ট্র্যাকিং: একটি Navigation অবজেক্ট প্রদান করে যা নেভিগেশন লাইফসাইকেলের সব পর্যায়ে অনন্য শনাক্তকারী হিসেবে কাজ করে।
  • সেভ করা স্টেট হেডার সাপোর্ট: অতিরিক্ত হেডার নির্ভরযোগ্যভাবে WebView স্টেট বান্ডেলে সেভ করা হয় যাতে স্টেট রিস্টোর করার সময় সেগুলি আবার ব্যবহার করা যায়।

মূল ক্ষমতা ও সীমাবদ্ধতা

WebViewCompat.navigate গ্রহণ করার আগে, নিম্নলিখিত অপারেশনাল নিয়ম ও সীমাবদ্ধতা বিবেচনা করুন:

  • থ্রেড নিরাপত্তা: আপনাকে অবশ্যই UI (প্রধান) থ্রেডে WebViewCompat.navigate কল করতে হবে।

  • বাতিল করা এবং অগ্রাধিকার: ইন-ফ্লাইট নেভিগেশন স্পষ্টভাবে বাতিল করা যাবে না। তবে, একই WebView-এ নতুন navigate কল শুরু করলে যেকোনও অ্যাক্টিভ নেভিগেশন বন্ধ হয়ে যায়।

  • URI স্কিম সাপোর্ট: স্ট্যান্ডার্ড (যেমন https: এবং http:) এবং কাস্টম URI স্কিম সাপোর্ট করা হয়। javascript: স্কিম কাজ করে না।

  • URL সাইজের সীমা: URL স্ট্রিংয়ের সর্বাধিক দৈর্ঘ্য ২ এমবি হতে পারে।

  • ফিচার চেক: বিভিন্ন WebView APK ভার্সন জুড়ে কম্প্যাটিবিলিটি বজায় রাখতে API কল করার আগে WebViewFeature.isFeatureSupported ব্যবহার করে সবসময় ফিচারের উপলভ্যতা চেক করুন।

নেভিগেশন ও ট্র্যাক লাইফসাইকেল শুরু করুন

নেভিগেশন কনফিগার করতে এবং এর লাইফসাইকেল ট্র্যাক করতে, নিম্নলিখিত ধাপগুলি অনুসরণ করুন:

  1. স্ট্রাকচার্ড লাইফসাইকেল কলব্যাক পেতে WebView সেট-আপের সময় WebViewCompat.addNavigationListener ব্যবহার করে NavigationListener ইমপ্লিমেন্টেশন রেজিস্টার করুন। মেমরি লিক ও ডুপ্লিকেট কলব্যাক এক্সিকিউশন প্রতিরোধ করতে, লিসেনার একবার রেজিস্টার করুন (প্রতিটি নেভিগেশন কলের পরিবর্তে)।
  2. NavigationParameters ইনস্ট্যান্স তৈরি করুন NavigationParameters.Builder ব্যবহার করে যাতে ঐচ্ছিক আচরণ নির্দিষ্ট করা যায়, যেমন ইতিহাস প্রতিস্থাপন বা কাস্টম HTTP হেডার।
  3. আপনার WebView ইনস্ট্যান্স, গন্তব্য URL এবং প্যারামিটার পাস করে WebViewCompat.navigate-এ কল করুন।

WebViewCompat.navigate একটি Navigation অবজেক্ট রিটার্ন করে যা অনন্যভাবে অনুরোধটি শনাক্ত করে। আপনার NavigationListener কলব্যাকে, নির্দিষ্ট নেভিগেশন ট্র্যাক করতে, ইনকামিং Navigation প্যারামিটারের সাথে এই অবজেক্টের তুলনা করুন।

প্রয়োগের উদাহরণ

নিচের উদাহরণে দেখানো হয়েছে যে কীভাবে নেভিগেশন প্যারামিটার কনফিগার করতে হয়, WebViewCompat.navigate কল করতে হয় এবং নেভিগেশন লাইফসাইকেল ইভেন্ট শুনতে হয়:

Kotlin

class WebNavigationManager(private val webView: WebView) {
    // Track the navigation instance returned by the API
    private var currentNavigation: Navigation? = null

    init {
        // 1. Define listener to observe navigation lifecycle events
        val listener = object : NavigationListener {
            override fun onNavigationStarted(navigation: Navigation) {
                if (navigation == currentNavigation) {
                    // Navigation started
                }
            }

            override fun onNavigationRedirected(navigation: Navigation) {
                if (navigation == currentNavigation) {
                    // Navigation encountered a redirect
                }
            }

            override fun onNavigationCompleted(navigation: Navigation) {
                if (navigation == currentNavigation) {
                    if (navigation.didCommit()) {
                        // Navigation committed successfully
                    } else if (navigation.didCommitErrorPage()) {
                        // Navigation committed an error page
                        val statusCode = navigation.statusCode
                        val error = navigation.webResourceError
                    }
                }
            }

            override fun onFirstContentfulPaintMillis(page: Page, durationMillis: Long) {
                // Match page with current navigation
                if (page == currentNavigation?.page) {
                    // Page rendering started (First Contentful Paint achieved)
                }
            }
        }

        // 2. Register listener on the main thread
        WebViewCompat.addNavigationListener(webView, listener)
    }

    @UiThread
    fun navigateToPage(url: String) {
        // Check feature availability
        if (!WebViewFeature.isFeatureSupported(WebViewFeature.WEBVIEW_NAVIGATE_EXPERIMENTAL_V1)) {
            // Fall back to standard loadUrl if navigate API is unavailable
            webView.loadUrl(url)
            return
        }

        // 3. Configure navigation parameters
        val params = NavigationParameters.Builder()
            .setShouldReplaceCurrentEntry(true)
            .addAdditionalHeaders(
                mapOf("X-Test-Navigate-Header" to "TestValue")
            )
            .build()

        // 4. Initiate navigation on the UI thread
        currentNavigation = WebViewCompat.navigate(webView, url, params)
    }
}

জাভা

public class WebNavigationManager {

    private Navigation mCurrentNavigation;
    private final WebView mWebView;

    public WebNavigationManager(@NonNull WebView webView) {
        mWebView = webView;
        setupListener();
    }

    private void setupListener() {
        // 1. Define listener to observe navigation lifecycle events
        NavigationListener listener = new NavigationListener() {
            @Override
            public void onNavigationStarted(@NonNull Navigation navigation) {
                if (navigation.equals(mCurrentNavigation)) {
                    // Navigation started
                }
            }

            @Override
            public void onNavigationRedirected(@NonNull Navigation navigation) {
                if (navigation.equals(mCurrentNavigation)) {
                    // Navigation encountered a redirect
                }
            }

            @Override
            public void onNavigationCompleted(@NonNull Navigation navigation) {
                if (navigation.equals(mCurrentNavigation)) {
                    if (navigation.didCommit()) {
                        // Navigation committed successfully
                    } else if (navigation.didCommitErrorPage()) {
                        // Navigation committed an error page
                        int statusCode = navigation.getStatusCode();
                        WebResourceErrorCompat error = navigation.getWebResourceError();
                    }
                }
            }

            @Override
            public void onFirstContentfulPaintMillis(@NonNull Page page, long durationMillis) {
                if (mCurrentNavigation != null && page.equals(mCurrentNavigation.getPage())) {
                    // Page rendering started (First Contentful Paint achieved)
                }
            }
        };

        // 2. Register listener on the main thread
        WebViewCompat.addNavigationListener(mWebView, listener);
    }

    @UiThread
    public void navigateToPage(@NonNull String url) {
        // Check feature availability
        if (!WebViewFeature.isFeatureSupported(WebViewFeature.WEBVIEW_NAVIGATE_EXPERIMENTAL_V1)) {
            // Fall back to standard loadUrl if navigate API is unavailable
            mWebView.loadUrl(url);
            return;
        }

        // 3. Configure navigation parameters
        NavigationParameters params = new NavigationParameters.Builder()
            .setShouldReplaceCurrentEntry(true)
            .addAdditionalHeaders(Collections.singletonMap(
                "X-Test-Navigate-Header", "TestValue"
            ))
            .build();

        // 4. Initiate navigation on the UI thread
        mCurrentNavigation = WebViewCompat.navigate(mWebView, url, params);
    }
}

HTTP হেডার ব্যবহার করে অ্যাপের স্ট্যাটাস প্রচার করুন

ব্যাকএন্ড লজিক সমন্বয় করতে বা ওয়েব কন্টেন্ট কাস্টমাইজ করতে ওয়েব অ্যাপের প্রায়শই হোস্ট Android অ্যাপ থেকে প্রসঙ্গের প্রয়োজন হয়। এই তথ্য পাস করার জন্য URL-এ কোয়েরি প্যারামিটার যোগ করলে URL-এ বিশৃঙ্খলা সৃষ্টি হতে পারে, ক্যাশিংয়ে বাধা আসতে পারে এবং অ্যাপের ইন্টার্নাল স্ট্যাটাস প্রকাশ হয়ে যেতে পারে।

পরিবর্তে, আমরা কাস্টম HTTP হেডার ব্যবহার করে অ্যাপের প্রসঙ্গ পাস করার সাজেশন দিই। WebViewCompat.navigate এবং NavigationParameters ব্যবহার করে, আপনি নিরাপদে এই ডেটা আপনার সার্ভারে পাঠাতে পারবেন। এছাড়াও, WebView স্টেট রিস্টোর করার সময় এইসব হেডার সংরক্ষণ করে, যা কনফিগারেশন পরিবর্তন জুড়ে ওয়েব কন্টেন্ট সামঞ্জস্যপূর্ণ থাকার বিষয়টি নিশ্চিত করে। মনে রাখবেন যে এই পারসিস্টেন্স শুধুমাত্র WebViewCompat.navigate ব্যবহার করার সময় প্রযোজ্য হয়। আপনি WebView.loadUrl ব্যবহার করলে, কাস্টম হেডার WebView স্টেট বান্ডেলে সেভ করা হয় না এবং রিস্টোর করার সময় হারিয়ে যায়।

সাধারণ ব্যবহারিক প্রয়োগ

হোস্ট অ্যাপের প্রসঙ্গ পাস করার সাধারণ ব্যবহারের ক্ষেত্রে নিম্নলিখিতগুলি অন্তর্ভুক্ত:

  • অ্যাপ ভার্সন (X-App-Version): হোস্ট অ্যাপের রিলিজ ভার্সন (যেমন BuildConfig.VERSION_NAME) পাস করলে, আপনার ব্যাকএন্ড সার্ভারকে নেটিভ জাভাস্ক্রিপ্ট ব্রিজ কম্প্যাটিবিলিটি, গেট ফিচার যাচাই করতে বা পুরনো অ্যাপ আপডেট করার জন্য ব্যবহারকারীকে প্রম্পট করতে সাহায্য করে।
  • ক্লায়েন্ট প্ল্যাটফর্ম (X-Client-Platform): হোস্ট এনভায়রনমেন্টকে Android হিসেবে স্পষ্টভাবে শনাক্ত করার ফলে, সার্ভার User-Agent স্ট্রিং পার্সিংয়ের উপর নির্ভর না করেই প্ল্যাটফর্ম-অনুযায়ী UI বা রুট স্টোর লিঙ্ক ডেলিভার করতে পারে।

প্রয়োগের উদাহরণ

নিম্নলিখিত উদাহরণে দেখানো হয়েছে যে কীভাবে ওয়েব সার্ভারে অ্যাপ্লিকেশন ভার্সন ও ক্লায়েন্ট প্ল্যাটফর্ম পাস করতে হয়:

Kotlin

// Attach host app metadata so the server can verify compatibility and tailor content
val params = NavigationParameters.Builder()
    .addAdditionalHeaders(
        mapOf(
            "X-App-Version" to BuildConfig.VERSION_NAME,
            "X-Client-Platform" to "Android"
        )
    )
    .build()

// Use navigate instead of loadUrl to retain custom headers across state restoration
WebViewCompat.navigate(webView, "https://www.example.com", params)

জাভা

// Attach host app metadata so the server can verify compatibility and tailor content
Map<String, String> headers = new HashMap<>();
headers.put("X-App-Version", BuildConfig.VERSION_NAME);
headers.put("X-Client-Platform", "Android");

NavigationParameters params = new NavigationParameters.Builder()
    .addAdditionalHeaders(headers)
    .build();

// Use navigate instead of loadUrl to retain custom headers across state restoration
WebViewCompat.navigate(webView, "https://www.example.com", params);

ব্যর্থতার মোড এবং সমস্যা ম্যানেজ করা

WebViewCompat.navigate API কনফিগারেশন সংক্রান্ত সমস্যা ও রানটাইম নেভিগেশন ব্যর্থতা ম্যানেজ করার জন্য আলাদা মেকানিজম প্রদান করে:

অবৈধ যুক্তি ব্যতিক্রমগুলি

ভুল আর্গুমেন্ট পাস করলে সিঙ্ক্রোনাস IllegalArgumentException ট্রিগার হয়। সাধারণত নিম্নলিখিত কারণে এটি হয়:

  • প্রয়োজনীয় নন-নাল প্যারামিটারের (webView, url, অথবা params) জন্য null পাস করা হচ্ছে।
  • কাজ করে না এমন URL স্কিম প্রদান করা, যেমন javascript:।
  • ভুলভাবে ফর্ম্যাট করা HTTP হেডার কী বা ভ্যালু পাস করা যা RFC 2616 স্পেসিফিকেশন মেনে চলে না।

নেটওয়ার্ক অনুরোধ বা পৃষ্ঠা লোড করার সময় কোনও সমস্যা হলে (যেমন, HTTP 404 স্ট্যাটাস কোড, DNS রেজোলিউশন সংক্রান্ত সমস্যা বা SSL সমস্যা), WebViewCompat.navigate এখনও সঠিক Navigation অবজেক্ট রিটার্ন করে।

নেভিগেশন শেষ হলে, ব্যর্থতার কারণ ডায়াগনসিস করতে আপনার onNavigationCompleted কলব্যাকের মধ্যে Navigation ইনস্ট্যান্সে নিম্নলিখিত পদ্ধতিগুলি পরীক্ষা করুন:

  • getStatusCode: HTTP রেসপন্স স্ট্যাটাস কোড (যেমন, 404 বা 500) রিটার্ন করে।
  • getWebResourceError: নেটওয়ার্ক সংক্রান্ত সমস্যার বিবরণ সহ WebResourceErrorCompat অবজেক্ট রিটার্ন করে, যেমন কানেকশন টাইমআউট বা হোস্ট লুকআপ ব্যর্থ হওয়া।
  • didCommitErrorPage: WebView ব্যবহারকারীকে কোনও সমস্যা সংক্রান্ত পেজ দেখিয়েছে কিনা তা নির্দেশ করে।
  • didCommit: নেভিগেশন বাতিল না হয়ে টার্গেট পৃষ্ঠায় সফলভাবে কমিট হয়েছে কিনা তা দেখায়।

সেভ স্টেট বান্ডেল ম্যানেজমেন্ট

আপনি NavigationParameters-এর সাথে অতিরিক্ত হেডার পাস করলে, WebView এই হেডারগুলি তার সেভ করা স্টেট বান্ডেলে সেভ করে যাতে স্টেট রিস্টোর করার সময় সেগুলি আবার ব্যবহার করা যায়। তবে, হেডারের বড় সংগ্রহ সেভ করা অবস্থার সাইজ Bundle উল্লেখযোগ্যভাবে বাড়িয়ে দিতে পারে।

Android স্টেট সেভ করার সময় TransactionTooLargeException আটকাতে বান্ডেলের সাইজ সীমিত করতে হলে, WebViewCompat.saveState ব্যবহার করুন। এই পদ্ধতি আপনাকে বাইটে বান্ডেলের সর্বাধিক সাইজের সীমা সেট করতে এবং ঐচ্ছিকভাবে ফরওয়ার্ড ইতিহাস আইটেম বাদ দিতে দেয়:

Kotlin

// Save state with a maximum bundle size limit (for example, 64 KB)
val maxSizeBytes = 64 * 1024
val includeForwardState = false
val outState = Bundle()

WebViewCompat.saveState(webView, outState, maxSizeBytes, includeForwardState)

জাভা

// Save state with a maximum bundle size limit (for example, 64 KB)
int maxSizeBytes = 64 * 1024;
boolean includeForwardState = false;
Bundle outState = new Bundle();

WebViewCompat.saveState(webView, outState, maxSizeBytes, includeForwardState);

এর ফলে তৈরি হওয়া বান্ডেল স্ট্যান্ডার্ড WebView.restoreState পদ্ধতির সাথে মানানসই হয়।

মাইগ্রেশন ও প্রয়োগ সংক্রান্ত সাজেশন

WebView-এ নেভিগেট করার সময় অপ্টিমাল পারফর্ম্যান্স ও স্থায়িত্ব নিশ্চিত করতে, এইসব সাজেশন অনুসরণ করুন:

  • loadUrl থেকে navigate-এ মাইগ্রেট করুন: সব লিগ্যাসি WebView.loadUrl কল WebViewCompat.navigate-এ মাইগ্রেট করুন। এটি নিশ্চিত করে যে ইতিহাস ব্যবস্থাপনা অভিন্ন এবং হেডারগুলি সর্বদা সংরক্ষিত অবস্থার অংশ হিসাবে সংরক্ষিত হয়।

  • ফিচার কাজ করে কিনা তা সবসময় যাচাই করুন: API চালু করার আগে, পুরনো WebView ভার্সনের ক্ষেত্রে সুরক্ষা নিশ্চিত করতে WebViewFeature.isFeatureSupported-এর সাথে রানটাইম সাপোর্ট কনফার্ম করুন।

  • নেভিগেশন ইনস্ট্যান্সের মধ্যে সম্পর্ক স্থাপন করা: একাধিক WebView ইনস্ট্যান্স ম্যানেজ করার সময় একই সাথে চলা নেভিগেশন বা ফিল্টার কলব্যাক আলাদা করতে ফেরত আসা Navigation অবজেক্ট ব্যবহার করুন।

  • ইনিশিয়ালাইজেশনের সময় একবার লিসেনার রেজিস্টার করুন: কারণ WebViewCompat.addNavigationListener আগে থেকে থাকা লিসেনারকে পরিবর্তন করার পরিবর্তে নতুন লিসেনার যোগ করে, তাই WebView সেট-আপের সময় একবার আপনার NavigationListener রেজিস্টার করুন যাতে মেমরি লিক এবং পরবর্তী নেভিগেশন জুড়ে ডুপ্লিকেট কলব্যাক এক্সিকিউশন এড়ানো যায়।

  • সেভ স্টেট সাইজ মনিটর করা: বড় হেডার পেলোড পাস করার সময়, অতিরিক্ত স্টেট ডেটা সেভ করা এড়াতে WebViewCompat.saveState নির্দিষ্ট সাইজ বাউন্ডারি সহ ব্যবহার করুন।

অতিরিক্ত রিসোর্স

এম্বেড করা ওয়েব ক্ষমতা ও পারফর্ম্যান্স অপ্টিমাইজেশন সম্পর্কে আরও জানতে, নিম্নলিখিত নির্দেশিকা দেখুন: