Gradle: Android এবং build.gradle-এর জন্য বিল্ড সিস্টেমের সারমর্ম

লেখক: IT Sectr প্রকাশিত: 2026-02-12 পড়ার সময়: 9 মিনিট

Gradle একটি বিল্ড সিস্টেম যা Android অ্যাপ্লিকেশনের কম্পাইলেশন, টেস্টিং এবং প্যাকেজিং স্বয়ংক্রিয় করে। Apache Ant বা Maven-এর থেকে ভিন্ন, এটি ইনক্রিমেন্টাল বিল্ড এবং ফলাফল ক্যাশিং সমর্থন করে। এর বৈশিষ্ট্য সম্পর্কে আরও জানতে অফিসিয়াল Gradle ডকুমেন্টেশন দেখুন। 2013 সাল থেকে, টুলটি Android Studio-তে Android প্রকল্পের জন্য মানক বিল্ড সিস্টেম হিসেবে ব্যবহৃত হচ্ছে।

মূল বিষয়

  • Gradle — 2013 সাল থেকে Android-এর জন্য মানক বিল্ড সিস্টেম, যা Ant এবং Maven-কে প্রতিস্থাপন করেছে
  • Build.gradle.kts Kotlin DSL সহ — টাইপ চেকিং সহ আধুনিক কনফিগারেশন মান
  • Build variants বিভিন্ন অ্যাপ সংস্করণের জন্য বিল্ড টাইপ এবং প্রোডাক্ট ফ্লেভার একত্রিত করে
  • প্লাগইন কার্যকারিতা বাড়ায়: Android টুল প্রয়োগ থেকে বিল্ড প্রকাশ করা পর্যন্ত
  • ইনক্রিমেন্টাল বিল্ড এবং ক্যাশিং পুনঃকম্পাইলেশন সময় কয়েকগুণ কমায়

Gradle কী?

Gradle একটি ওপেন-সোর্স বিল্ড অটোমেশন টুল যা Java-তে লেখা এবং JVM-তে চলে। এটি ইনপুট হিসেবে সোর্স কোড, নির্ভরতা এবং রিসোর্স নেয় এবং আউটপুট হিসেবে একটি প্রস্তুত অ্যাপ্লিকেশন — Android-এর জন্য APK বা AAB — উৎপন্ন করে। এর মূল অংশে, Gradle টাস্কের নির্দেশিত অ্যাসাইক্লিক গ্রাফ (DAG) ধারণা ব্যবহার করে, যেখানে প্রতিটি টাস্ক কাজের একটি পারমাণবিক একক এবং তাদের মধ্যে সংযোগ নির্বাহের ক্রম নির্ধারণ করে। Make বা Ant-এর থেকে ভিন্ন, Gradle-কে ম্যানুয়ালি ধাপের ক্রম বর্ণনা করার প্রয়োজন নেই: টাস্কের মধ্যে নির্ভরতা ঘোষণা করাই যথেষ্ট, এবং সিস্টেম নিজেই সর্বোত্তম ক্রম নির্ধারণ করবে। এই পদ্ধতি Gradle-কে যেকোনো আকারের প্রকল্পের জন্য নমনীয় এবং স্কেলযোগ্য করে তোলে।

সিস্টেম তিনটি নির্বাহ পর্যায় ব্যবহার করে: আরম্ভকরণ (অংশগ্রহণকারী প্রকল্প চিহ্নিতকরণ), কনফিগারেশন (টাস্ক গ্রাফ নির্মাণ) এবং নির্বাহ (প্রয়োজনীয় ক্রমে টাস্ক চালানো)। কনফিগারেশন পর্যায়টি Gradle-এর একটি মূল বৈশিষ্ট্য: টাস্ক শুরু হওয়ার আগে সম্পূর্ণ বিল্ড স্ক্রিপ্ট চলে, যা শর্ত অনুযায়ী গ্রাফে গতিশীল পরিবর্তনের অনুমতি দেয়। এটি সম্ভব করে, উদাহরণস্বরূপ, কোড নকল না করে শুধুমাত্র নির্দিষ্ট বিল্ড ভেরিয়েন্টের জন্য টাস্ক যোগ করা। বিল্ডারটি Groovy-তে লেখা, কিন্তু কনফিগারেশন ফাইল দুটি ভাষা সমর্থন করে: Groovy DSL এবং Kotlin DSL।

Gradle কীভাবে Android প্রকল্প বিল্ড পরিচালনা করে?

Gradle-এর জন্য Android প্লাগইন com.android.application এবং com.android.library নিয়ে গঠিত, যা Android টুলের সাথে কাজ করার জন্য প্রকল্পে টাস্ক যোগ করে। যখন একজন ডেভেলপার বিল্ড শুরু করে, Gradle ক্রমান্বয়ে ডজনখানেক টাস্ক নির্বাহ করে: javac বা kotlinc-এর মাধ্যমে Kotlin এবং Java কম্পাইল করা, AAPT2-এর মাধ্যমে রিসোর্স প্রক্রিয়াকরণ, R.java তৈরি করা, D8 বা R8-এর মাধ্যমে বাইটকোডকে DEX-এ কম্পাইল করা, APK সাইন এবং জিপ করা। প্রতিটি টাস্ক চেক করে তার ইনপুট ডেটা পরিবর্তিত হয়েছে কিনা, এবং যদি না হয়, ক্যাশ করা ফলাফল ব্যবহার করে। এই প্রক্রিয়াটিকে ইনক্রিমেন্টাল বিল্ড বলা হয় এবং এটি পূর্ণ পুনর্নির্মাণের তুলনায় পুনঃকম্পাইলেশন 60–80% দ্রুত করে।

Android মডিউল কনফিগারেশন build.gradle.kts ফাইলের android ব্লকে সেট করা হয়। ব্লকের ভিতরে, compileSdk, minSdk, targetSdk, অ্যাপ সংস্করণ, স্বাক্ষর এবং অন্যান্য প্যারামিটার সংজ্ঞায়িত করা হয়। Gradle স্বয়ংক্রিয়ভাবে প্রতিটি মডিউলের জন্য একাধিক বিল্ড ভেরিয়েন্ট তৈরি করে — টাইপ (release, debug) এবং ফ্লেভারের সংমিশ্রণ। উদাহরণস্বরূপ, দুটি ফ্লেভার এবং দুটি টাইপের মডিউলের জন্য, Gradle চারটি টাস্ক উৎপন্ন করে: assembleDemoDebug, assembleDemoRelease, assembleFullDebug, assembleFullRelease। এই সমস্ত টাস্ক পৃথকভাবে নির্বাহ করা যেতে পারে বা একই কমান্ড দিয়ে সব ভেরিয়েন্টের জন্য একসাথে চালানো যেতে পারে।

Build.gradle এবং build.gradle.kts: কনফিগারেশন কাঠামো

প্রতিটি Android প্রকল্পে দুই স্তরের কনফিগারেশন থাকে: রুট build.gradle.kts (সব মডিউলের জন্য সেটিংস) এবং মডিউল-স্তরের build.gradle.kts (একটি নির্দিষ্ট মডিউলের জন্য সেটিংস)। রুট ফাইলে প্লাগইন প্রয়োগ না করে ঘোষণা করা হয়, রিপোজিটরি এবং সাধারণ ভেরিয়েবল। মডিউল ফাইলে, প্লাগইন নির্দিষ্ট মডিউলে প্রয়োগ করা হয় এবং বিল্ড প্যারামিটার কনফিগার করা হয়। এই পদ্ধতি সংস্করণ ক্যাটালগ বা ext-ব্লকের মাধ্যমে নির্ভরতা সংস্করণের কেন্দ্রীভূত ব্যবস্থাপনার অনুমতি দেয়।

Kotlin
@Suppress("UnstableApiUsage")
plugins {
    id("com.android.application") version "8.2.2"
    id("org.jetbrains.kotlin.android") version "1.9.22"
}

android {
    namespace = "com.example.myapp"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.example.myapp"
        minSdk = 24
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"
    }
}

dependencies ব্লক build.gradle.kts-এর আরেকটি গুরুত্বপূর্ণ উপাদান। এটি অ্যাপ্লিকেশনের প্রয়োজনীয় লাইব্রেরি, মডিউল এবং ফাইল নির্ভরতা তালিকাভুক্ত করে। Gradle একাধিক নির্ভরতা কনফিগারেশন সমর্থন করে: implementation (শুধুমাত্র বর্তমান মডিউলের জন্য উপলব্ধ), api (নির্ভরশীল মডিউলের জন্যও উপলব্ধ), testImplementation (শুধুমাত্র পরীক্ষার জন্য), androidTestImplementation (ইনস্ট্রুমেন্টেড পরীক্ষার জন্য) এবং compileOnly (শুধুমাত্র কম্পাইলেশনে)। প্রতিটি কনফিগারেশন নির্ভরতা গ্রাফে ক্লাস দৃশ্যমানতা পরিচালনা করে, যা বিল্ড সময় এবং চূড়ান্ত আর্টিফ্যাক্ট আকারকে প্রভাবিত করে।

Kotlin
dependencies {
    implementation("androidx.core:core-ktx:1.12.0")
    implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
    implementation("androidx.activity:activity-compose:1.8.2")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
}

Build variants: অ্যাপ বিল্ড অপশন

Build variant হল বিল্ড টাইপ এবং প্রোডাক্ট ফ্লেভারের সংমিশ্রণ যা অনন্য সেটিংস, কোড এবং রিসোর্স সহ অ্যাপ সংস্করণ সংজ্ঞায়িত করে। বিল্ড টাইপ প্যাকেজিং প্যারামিটার সংজ্ঞায়িত করে: debug (ডিবাগিং এবং .debug প্রত্যয় সহ) বা release (অস্পষ্টকরণ এবং স্বাক্ষর সহ)। প্রোডাক্ট ফ্লেভার কার্যকরী ভেরিয়েন্ট সংজ্ঞায়িত করে: উদাহরণস্বরূপ, demo (সীমিত সংস্করণ) এবং full (অতিরিক্ত বৈশিষ্ট্য সহ পূর্ণ সংস্করণ)। Gradle স্বয়ংক্রিয়ভাবে প্রতিটি সংমিশ্রণের জন্য টাস্ক তৈরি করে, যা একটি একক কমান্ড দিয়ে সব সংস্করণ বিল্ড করার অনুমতি দেয়।

Kotlin
android {
    buildTypes {
        release {
            isMinifyEnabled = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
        debug {
            applicationIdSuffix = ".debug"
        }
    }
    flavorDimensions += "version"
    productFlavors {
        create("demo") {
            dimension = "version"
            applicationIdSuffix = ".demo"
        }
        create("full") {
            dimension = "version"
            applicationIdSuffix = ".full"
        }
    }
}

প্রতিটি build variant-এর একটি পৃথক সোর্স সেট থাকে। Gradle ডিরেক্টরি src/demo/release, src/full/debug এবং অন্যান্য ব্যবহার করে, যা একটি নির্দিষ্ট ভেরিয়েন্টের জন্য অনন্য রিসোর্স, ম্যানিফেস্ট এবং সোর্স ফাইল সংরক্ষণ করে। সাধারণ কোড src/main-এ থাকে। এই পদ্ধতি মূল লজিক পুনঃব্যবহার এবং শুধুমাত্র ভিন্ন অংশ প্রতিস্থাপনের অনুমতি দেয়: স্ট্রিং, আইকন, API এন্ডপয়েন্ট বা কনফিগারেশন ফাইল। একটি সোর্স সেট main থেকে যেকোনো রিসোর্স ওভাররাইড করতে পারে: ম্যানিফেস্ট, drawable, values বা এমনকি Kotlin ক্লাস। একটি নির্দিষ্ট ভেরিয়েন্ট বিল্ড করার সময়, Gradle main এবং সংশ্লিষ্ট সোর্স সেট থেকে ফাইল মার্জ করে, যেখানে ভেরিয়েন্টের ফাইলগুলি অগ্রাধিকার পায়।

Android-এর জন্য Gradle প্লাগইন: সক্ষমতা সম্প্রসারণ

Gradle প্লাগইন ইকোসিস্টেম Android অ্যাপ্লিকেশন ডেভেলপমেন্টের সব ধাপ কভার করে। Google-এর অফিসিয়াল প্লাগইনের মধ্যে রয়েছে com.android.application (অ্যাপ মডিউলের জন্য), com.android.library (লাইব্রেরি মডিউলের জন্য), com.android.test (পরীক্ষা মডিউলের জন্য) এবং JetBrains-এর Kotlin প্লাগইন। প্লাগইন প্রকল্পে নতুন টাস্ক যোগ করে, DSL-কে নতুন কনফিগারেশন ব্লক দিয়ে সম্প্রসারিত করে এবং অতিরিক্ত টুল সংযুক্ত করে। com.android.application প্লাগইন ছাড়া, একটি প্রকল্প APK বিল্ড করতে পারে না: এই প্লাগইন সব Android-নির্দিষ্ট টাস্ক নিবন্ধন করে এবং সেগুলিকে বিল্ড গ্রাফে সংযুক্ত করে।

থার্ড-পার্টি প্লাগইন আরও নির্দিষ্ট কাজ সমাধান করে। Google Services (com.google.gms.google-services) Firebase এবং Google Play Services-কে সংহত করে, স্বয়ংক্রিয়ভাবে google-services.json বিল্ডে সন্নিবেশ করে। Hilt (dagger.hilt.android.plugin) কম্পাইলেশনে নির্ভরতা ইনজেকশন কোড তৈরি করে। Safe Args (androidx.navigation.safeargs.kotlin) ফ্র্যাগমেন্টের মধ্যে নেভিগেশনের জন্য টাইপ-সেফ ক্লাস তৈরি করে। প্রতিটি প্লাগইন রুট build.gradle.kts-এ প্লাগইন ব্লকের মাধ্যমে যোগ করা হয় এবং সাধারণত ন্যূনতম কনফিগারেশন প্রয়োজন। Gradle স্বয়ংক্রিয়ভাবে প্লাগইনের মধ্যে ট্রানজিটিভ নির্ভরতা সমাধান করে এবং Bom ফাইল এবং সংস্করণ ক্যাটালগের মাধ্যমে সংস্করণ সামঞ্জস্য নিশ্চিত করে।

Gradle টাস্ক: বিল্ড প্রক্রিয়া স্বয়ংক্রিয়করণ

টাস্ক হল Gradle-এ কাজের একটি পারমাণবিক একক। প্রতিটি টাস্কের ইনপুট ডেটা, আউটপুট ডেটা এবং একটি ক্রিয়া থাকে। Android-এর জন্য অন্তর্নির্মিত টাস্কের মধ্যে রয়েছে assemble (সব ভেরিয়েন্ট বিল্ড), lint (কোড পরীক্ষা), test (ইউনিট টেস্ট চালানো) এবং clean (অস্থায়ী ফাইল পরিষ্কার)। ডেভেলপাররা Groovy বা Kotlin DSL ব্যবহার করে নিজস্ব টাস্ক যোগ করতে পারেন। কাস্টম টাস্ক রুটিন অপারেশন স্বয়ংক্রিয় করার জন্য উপযোগী: রিপোর্ট তৈরি, আর্টিফ্যাক্ট কপি, পরীক্ষা ডিভাইসে ডিপ্লয় বা CI সিস্টেমের সাথে সংহতকরণ।

Kotlin
tasks.register("printBuildInfo") {
    description = "বিল্ড তথ্য প্রদর্শন করে"
    group = "custom"
    doLast {
        println("Build variant: ${project.name}")
        println("Version: ${android.defaultConfig.versionName}")
    }
}

প্রতিটি টাস্ক dependsOn প্রক্রিয়ার মাধ্যমে অন্যান্য টাস্কের উপর নির্ভর করতে পারে। যদি টাস্ক A, টাস্ক B-এর উপর নির্ভর করে, Gradle নিশ্চিত করে যে B, A-এর আগে নির্বাহ হবে। সিস্টেমের প্রতিটি জোড়ার জন্য ম্যানুয়ালি ক্রম নির্দিষ্ট করার প্রয়োজন নেই — নির্ভরতা ঘোষণা করাই যথেষ্ট, এবং Gradle স্বাধীন টাস্কের সমান্তরাল নির্বাহের জন্য অপ্টিমাইজ করা একটি নির্দেশিত গ্রাফ তৈরি করবে। Android প্লাগইনের অন্তর্নির্মিত টাস্ক ইতিমধ্যে সংযুক্ত: lint কম্পাইলেশনের উপর নির্ভর করে, test assemble-এর উপর নির্ভর করে, assembleDebug compileDebugKotlin-এর উপর নির্ভর করে। ডেভেলপাররা dependsOn, mustRunAfter বা shouldRunAfter ব্যবহার করে গ্রাফের যেকোনো নোডে নিজস্ব টাস্ক সন্নিবেশ করতে পারেন।

Gradle-এর সাথে কাজ করার সময় সাধারণ ভুল

একটি সাধারণ সমস্যা হল নির্ভরতা সংস্করণ দ্বন্দ্ব, যখন দুটি লাইব্রেরির একই ট্রানজিটিভ নির্ভরতার ভিন্ন সংস্করণ প্রয়োজন হয়। Gradle দ্বন্দ্ব ত্রুটি রিপোর্ট করে, কিন্তু সবসময় স্বয়ংক্রিয় সমাধান দেয় না। নির্ণয়ের জন্য, ./gradlew :app:dependencies কমান্ড ব্যবহার করুন, যা সম্পূর্ণ নির্ভরতা ট্রি আউটপুট করে। resolutionStrategy ব্লকের মাধ্যমে দ্বন্দ্বকারী লাইব্রেরির সংস্করণ জোর করে নির্দিষ্ট করার সুপারিশ করা হয়। আরেকটি সাধারণ পরিস্থিতি হল ধীর বিল্ড ইনক্রিমেন্টাল প্রক্রিয়াকরণের অভাবের কারণে। নিশ্চিত করুন যে সব প্লাগইন আপডেট আছে, Gradle Daemon সক্ষম (org.gradle.daemon=true) এবং gradle.properties-এ পর্যাপ্ত মেমরি সেট আছে: org.gradle.jvmargs=-Xmx4096m।

ক্যাশিং সমস্যা নির্ভরতা আপডেট করার পর দেখা দেয়: Gradle পুরনো ক্যাশ ব্যবহার করতে পারে এবং বিল্ড ব্যর্থ হয়। সমাধান — --refresh-dependencies ফ্ল্যাগ দিয়ে বিল্ড চালানো বা ./gradlew cleanBuildCache-এর মাধ্যমে ম্যানুয়ালি ক্যাশ পরিষ্কার করা। তৃতীয় সবচেয়ে সাধারণ ত্রুটি হল Android Gradle Plugin (AGP) এবং Gradle-এর মধ্যে সংস্করণ অসামঞ্জস্যতা। প্রতিটি AGP সংস্করণের একটি নির্দিষ্ট ন্যূনতম Gradle সংস্করণ প্রয়োজন। সামঞ্জস্য সারণী developer.android.com-এ প্রকাশিত হয়। যদি সংস্করণ অসামঞ্জস্যপূর্ণ হয়, Gradle কনফিগারেশন পর্যায়ে ন্যূনতম প্রয়োজনীয় সংস্করণ সম্পর্কে বার্তা দিয়ে ব্যর্থ হয়। সর্বদা পরীক্ষা করুন যে Gradle wrapper সংস্করণ AGP প্রয়োজনীয়তার সাথে মেলে।

সচরাচর জিজ্ঞাসিত প্রশ্ন

সহজ ভাষায় Gradle কী?

Gradle প্রকল্প বিল্ড করার জন্য একটি প্রোগ্রাম-অটোমেটর। এটি Kotlin বা Java-তে আপনার সোর্স কোড নেয়, ইন্টারনেট থেকে লাইব্রেরি সংযোগ করে, সবকিছু বাইটকোডে কম্পাইল করে এবং APK-তে প্যাকেজ করে। এটি JVM-তে চলে এবং ম্যানুয়াল নির্দেশের পরিবর্তে ঘোষণামূলক স্ক্রিপ্ট ব্যবহার করে। ডেভেলপারকে শুধুমাত্র নিয়ম বর্ণনা করতে হবে, এবং Gradle বাকিটা করে।

build.gradle.kts build.gradle থেকে কীভাবে আলাদা?

Build.gradle Groovy-তে লেখা হয় — একটি গতিশীল ভাষা যার নমনীয় সিনট্যাক্স এবং কম কঠোরতা। Build.gradle.kts Kotlin DSL ব্যবহার করে: শক্তিশালী টাইপিং, Android Studio-তে অটোকমপ্লিট এবং কম্পাইলেশনে ত্রুটি পরীক্ষা। Google সব নতুন প্রকল্পের জন্য Kotlin DSL সুপারিশ করে। Groovy ফাইল মাইগ্রেট করা সহজ, কিন্তু Kotlin ফাইল রক্ষণাবেক্ষণে বেশি নির্ভরযোগ্য।

Gradle বিল্ড কীভাবে দ্রুত করবেন?

Gradle Daemon (org.gradle.daemon=true) এবং সমান্তরাল বিল্ড (org.gradle.parallel=true) সক্ষম করুন। org.gradle.jvmargs-এর মাধ্যমে JVM মেমরি 4–8 GB-তে বাড়ান। অন-ডিমান্ড প্রকল্প কনফিগারেশন (org.gradle.configureondemand=true) ব্যবহার করুন। Android প্রকল্পের জন্য, টাস্ক ক্যাশিং কনফিগার করুন এবং শুধুমাত্র প্রয়োজনীয় ABI-র জন্য বিল্ড করুন। Android Studio-তে, বাধা খুঁজতে Build Analyzer চালান।

Android-এ build variant কী?

Build variant হল বিল্ড টাইপ (যেমন, debug বা release) এবং প্রোডাক্ট ফ্লেভার (যেমন, demo বা full) এর সংমিশ্রণ। প্রতিটি ভেরিয়েন্টের নিজস্ব প্যাকেজ নাম, সংস্করণ, রিসোর্স এবং সোর্স ফাইল থাকতে পারে। Gradle স্বয়ংক্রিয়ভাবে প্রতিটি ভেরিয়েন্টের জন্য একটি পৃথক বিল্ড টাস্ক তৈরি করে। এটি একটি একক প্রকল্প থেকে অ্যাপ্লিকেশনের একাধিক সংস্করণ বিল্ড করার অনুমতি দেয়।

Gradle-এ নির্ভরতা কীভাবে যোগ করবেন?

নির্ভরতা build.gradle.kts ফাইলের dependencies ব্লকে যোগ করা হয়। ফরম্যাট হল: configuration("group:artifact:version")। উদাহরণস্বরূপ, implementation("androidx.core:core-ktx:1.12.0")। পরীক্ষার জন্য testImplementation ব্যবহার করুন, ইনস্ট্রুমেন্টেড পরীক্ষার জন্য — androidTestImplementation। libs.versions.toml ফাইলের মাধ্যমে একটি পৃথক সংস্করণ ক্যাটালগে সংস্করণ রাখা সুবিধাজনক।

সারাংশ

  • Gradle — Android-এর জন্য মানক বিল্ড সিস্টেম, JVM-তে চলে এবং টাস্কের DAG ব্যবহার করে
  • ইনক্রিমেন্টাল বিল্ড এবং ক্যাশিং পুনঃকম্পাইলেশন সময় 60–80% কমায়
  • Kotlin DSL (build.gradle.kts) — অটোকমপ্লিট এবং টাইপ চেকিং সহ আধুনিক কনফিগারেশন ফরম্যাট
  • Build variants বিল্ড টাইপ এবং প্রোডাক্ট ফ্লেভার একত্রিত করে, প্রতিটি ভেরিয়েন্টের জন্য পৃথক সোর্স সেট তৈরি করে
  • প্লাগইন Gradle-কে সম্প্রসারিত করে: মৌলিক Android প্লাগইন থেকে Firebase, Hilt এবং Safe Args পর্যন্ত
  • কাস্টম টাস্ক যেকোনো বিল্ড এবং সংহতকরণ ধাপ স্বয়ংক্রিয় করতে দেয়
  • সাধারণ সমস্যা — সংস্করণ দ্বন্দ্ব, ধীর বিল্ড এবং Gradle সংস্করণের সাথে AGP অসামঞ্জস্যতা

আমরা একটি মোবাইল অ্যাপ্লিকেশন টার্নকি তৈরি করব

IT Sectr 2017 সাল থেকে স্টার্টআপ এবং ব্যবসার জন্য iOS এবং Android অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।

প্রকল্প নিয়ে আলোচনা করুন

আরও পড়ুন