Author

admin

Browsing

DeepSeek API Integration In Flutter

DeepSeek’s large language models have become popular because of their efficiency and relatively lower cost compared to many competing LLMs. They have also released open models under the MIT license, making them accessible to developers for building and experimenting with AI applications.

In this tutorial, let’s learn how to integrate the **DeepSeek API into a Flutter app** and build a simple AI-powered application.

Before starting the project, You’ll need

deepseek flutter api integration

This is what we’ll build.

By default, using an API key directly in the Flutter code is not a good practice. So, for this tutorial, we’ll pass the API key through a text field. For a real application, you can use flutter_secure_storage to store it securely.
We use dependencies,

  • http — For API integration.
  • flutter_markdown_plus — LLMs typically return responses in Markdown format. This package helps us render the Markdown response in a more readable and visually appealing way.

ApiService.dart


import 'dart:convert';

import 'package:http/http.dart' as http;

class ApiService {
  static const _url = "https://api.deepseek.com/chat/completions";
  static const _model = "deepseek-v4-pro";

  static Future sendPrompt(String prompt, String apiKey) async {
    final response = await http.post(
      Uri.parse(_url),
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer $apiKey',
      },
      body: jsonEncode({
        'model': _model,
        'messages': [
          {'role': 'user', 'content': prompt},
        ],
      }),
    );

    if (response.statusCode != 200) {
      return 'Error: ${response.statusCode} - ${response.body}';
    }

    final data = jsonDecode(response.body);
    final content = data['choices'][0]['message']['content'];

    return content;
  }
}

  • This code is the simplest version of DeepSeek API integration.
  • We need to pass the API key in Bearer format in the headers.
  • Our query needs to be passed in a messages list, which contains the role as user and the content as your prompt.
  • The response data is stored inside the choices JSON array as the first object.

main.dart


import 'package:deepseek_api_integration/api_service.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';

void main() => runApp(const DeepSeekApp());

class DeepSeekApp extends StatefulWidget {
  const DeepSeekApp({super.key});

  @override
  State createState() => _DeepSeekAppState();
}

class _DeepSeekAppState extends State {
  bool _hideApiKey = true;

  final TextEditingController _apiKeyController = TextEditingController();
  final TextEditingController _promptController = TextEditingController();

  String _response = '';
  bool _loading = false;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'DeepSeek',
      debugShowCheckedModeBanner: false,
      home: SafeArea(
        child: Scaffold(
          appBar: AppBar(title: const Text('DeepSeek')),
          body: Padding(
            padding: const EdgeInsets.all(16.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                TextField(
                  obscureText: _hideApiKey,
                  controller: _apiKeyController,
                  decoration: InputDecoration(
                    labelText: 'API Key',
                    hintText: 'sk-...',
                    suffixIcon: IconButton(
                      icon: Icon(
                        _hideApiKey
                            ? Icons.visibility_outlined
                            : Icons.visibility_off_outlined,
                      ),
                      onPressed: () {
                        // Handle visibility toggle
                        setState(() {
                          _hideApiKey = !_hideApiKey;
                        });
                      },
                    ),
                    border: OutlineInputBorder(),
                  ),
                ),
                const SizedBox(height: 12),
                TextField(
                  minLines: 3,
                  maxLines: 6,
                  controller: _promptController,
                  decoration: InputDecoration(
                    labelText: 'Prompt',
                    hintText: 'Enter a prompt...',
                    border: OutlineInputBorder(),
                  ),
                ),
                const SizedBox(height: 12),
                FilledButton(
                  onPressed: _loading ? null : _submit,
                  child: Text(_loading ? 'Loading' : 'Submit'),
                ),
                const SizedBox(height: 12),
                Expanded(
                  child: _response.isEmpty
                      ? const Text('Response will appear here...')
                      : Markdown(data: _response),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }

  @override
  void dispose() {
    _apiKeyController.dispose();
    _promptController.dispose();
    super.dispose();
  }

  Future _submit() async {
    final apiKey = _apiKeyController.text.trim();
    final prompt = _promptController.text.trim();

    if (apiKey.isEmpty | prompt.isEmpty | _loading) return;

    setState(() {
      _loading = true;
      _response = '';
    });

    try {
      final response = await ApiService.sendPrompt(prompt, apiKey);

      setState(() {
        _loading = false;
        _response = response;
      });
    } catch (error) {
      setState(() {
        _loading = false;
        _response = error.toString();
      });
    }
  }
}


  • Here, the _submit method gets the data from both the API key and prompt text fields and passes it to the ApiService.sendPrompt method.
  • Before and after the API integration, we’ll update the state and refresh the UI using the setState() method.
  • Always add a try-catch block around your API integration code to handle exceptions.

Thats all for today.

ERRUNKNOWNURL_SCHEME Issue Fix In Android WebView​ – Latest solution added.

One of the issues we face in WebView is the ERR_UNKNOWN_URL_SCHEME error when different URL schemes are not supported by your WebView. Whether it’s happening on your own webpage or someone else’s, it’s still a bad user experience.

I’ve already discussed this in my Android WebView guide. So today, I’m going to discuss this in detail and show you the latest solution.

Because from Android 11, our app cannot completely see or query all the other apps installed on the device. This is actually done for user privacy, and Google also provides a way to handle this by using the tag.

So, we will learn about resolveActivity, the tag, and how to call other apps using their package name and Intent signature.

So, I’ve already created an Android project, and I’ve also created a demo.html file with most of the schemes that can be useful, and I’ve placed it inside the `assets` directory.

I’m using findViewById, not View Binding or Data Binding, so the process can be simpler and easier for you.

err_unknown_url_scheme android webview whatsapp, tel

What we’ll build today

demo.html





    
    

    Android URL Schemes

    









  • By default, Android WebView supports http and https schemes. The problem occurs when the scheme is not related to a webpage, like tel, whatsapp, instagram, UPI, and more.
  • Here, I created style resources for formatting the scheme text and URL text.
  • The android-app scheme is used to open a specific app by using its package name.
  • UPI schemes are one of the commonly used schemes for making UPI payments.
  • vnd.youtube+YOUTUBE_ID is the way to create a YouTube video scheme URL.

package com.ar.errorunknownschemewebviewandroid


import android.annotation.SuppressLint
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.webkit.URLUtil
import androidx.appcompat.app.AppCompatActivity
import android.webkit.WebView
import android.webkit.WebViewClient
import android.webkit.WebResourceRequest
import android.webkit.WebSettings
import android.widget.Toast
import androidx.activity.enableEdgeToEdge
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat

class MainActivity : AppCompatActivity() {

    lateinit var webView: WebView

    override fun onBackPressed() {
        if (webView.canGoBack()) {
            webView.goBack()
        } else {
            super.onBackPressed()
        }
    }

    @SuppressLint("MissingInflatedId")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_main)
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }

        webView = findViewById(R.id.webView)
        webView.loadUrl("file:///android_asset/demo.html")
        webView.webViewClient = MyWebViewClient()


        webView.settings.apply {
            javaScriptEnabled = true
            domStorageEnabled = true
            loadsImagesAutomatically = true
            mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
        }

    }

    inner class MyWebViewClient : WebViewClient() {

        @Deprecated("Deprecated in Java")
        override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {
            return handleUrl(url)
        }

        override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
            return handleUrl(request?.url.toString())
        }

        private fun handleUrl(url: String?): Boolean {
            if (url.isNullOrEmpty()) return false

            // Let WebView handle normal web links
            if (URLUtil.isNetworkUrl(url)) {
                return false
            }

            if(url.startsWith("myapp")){
                val intent = Intent(this@MainActivity, SecondActivity::class.java)
                startActivity(intent)
                return  true
            }

            try {
                val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))

                if(intent.resolveActivity(packageManager)!= null){
                    startActivity(intent)
                }
                else
                {
                    Toast.makeText(baseContext, "No app found to open this link", Toast.LENGTH_SHORT).show()
                }

            }
            catch (e: Exception){
                Toast.makeText(baseContext, "Exception: inside exception", Toast.LENGTH_SHORT).show()
            }

            return true
        }
    }

}




  • Here, I used MyWebViewClient, a subclass of WebViewClient, and overrode the shouldOverrideUrlLoading method. The old method was deprecated in API 24, and the latest version with WebResourceRequest is called before loading the URL in the WebView.
  • So, this is where our app gets control over the URL or scheme.
  • We will use a separate method called handleUrl, and we will handle our fixes here.
  • Returning false means let the WebView take care of the URL, and true means let our app take care of it.
  • resolveActivity() is used to check whether an app is available without getting an ActivityNotFoundException. If it is not null, we can call the startActivity() method to launch it.

AndroidManifest.xml





    

    
        
        

        
            

            
        
        
            

            
        
        
            

            
        
        
            

            
        
        
            

            
        
        
            

            
        
        
            

            
        
        
            

            
        
        
            

            
        
    

    
        
            
                

                
            
            
                

                
                

                
            
        
        
    



  • We need to provide the <queries> element to add the package name or Intent signature so Android can find the apps that our app needs to interact with.
  • In the Intent signature, we need to provide the action and scheme data.
  • We put a separate Intent filter to handle the myapp scheme.

Thats all for now.

Flutter AI Chat App: ChatGpt, Claude, Gemini and Grok In One Flutter App

Do you search the same prompt across different AI models?

I do it too.

So I thought, why not build a simple Flutter app that lets you chat with multiple AI models from one place?

In this tutorial, we’ll integrate GPT, Claude, Gemini, and Grok into a single Flutter app.

Let’s start building it.

flutter ai chat app

  • Access multiple AI models without switching between apps.
  • Full Markdown support, including headings, tables, and lists.
  • Tap URL in the response to open it in app.

https://youtu.be/yzkcDEvCok4
okay enough talk…Let’s start build

Okay… Let’s make a simple Flutter project named **`multi_chat`** using the `flutter create` command.

If you don’t know how to make a Flutter project read this command guide and Flutter Android Studio setup guide.

lets add the packages in pubspec.yaml file and also setup .env setup declaration.


name: multi_chat
description: "A new Flutter project."
publish_to: 'none' 

version: 1.0.0+1

environment:
  sdk: ^3.12.2

dependencies:
  flutter:
    sdk: flutter

  cupertino_icons: ^1.0.8
  http: ^1.6.0
  flutter_spinkit: ^5.2.2
  flutter_markdown_plus: ^1.0.12
  url_launcher: ^6.3.0
  flutter_dotenv: ^6.0.1

dev_dependencies:
  flutter_test:
    sdk: flutter

  flutter_lints: ^6.0.0

flutter:

  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  assets:
    - .env



  • http: For API integration.
  • flutter_spinkit: For showing a loading indicator.
  • flutter_markdown_plus: Converts AI responses into Markdown. It is the successor to flutter_markdown by Flutter.
  • url_launcher: Opens web URLs in the response.
  • flutter_dotenv: Loads API keys, still its not secure for productions apps.

main.dart
we will setup UI in main.dart file.


import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:multi_chat/models/llm_provider.dart';
import 'package:multi_chat/services/llm_service.dart';
import 'package:url_launcher/url_launcher.dart';

Future main() async {
  await dotenv.load();
  runApp(
    const MaterialApp(
      title: 'MultiChat',
      debugShowCheckedModeBanner: false,
      home: MultiChatScreen(),
    ),
  );
}

class MultiChatScreen extends StatefulWidget {
  const MultiChatScreen({super.key});

  @override
  State createState() => _MultiChatScreenState();
}

class _MultiChatScreenState extends State {
  final TextEditingController _searchController = TextEditingController();
  bool loading = false;
  String _response = '';

  LLMProvider _selectedProvider = LLMProvider.gemini;

  String _providerTitle(LLMProvider provider) => switch (provider) {
    LLMProvider.gemini => 'Gemini',
    LLMProvider.claude => 'Claude',
    LLMProvider.gpt => 'GPT',
    LLMProvider.grok => 'Grok',
  };

  String _providerSubTitle(LLMProvider provider) => switch (provider) {
    LLMProvider.gemini => "Google's AI Model",
    LLMProvider.claude => "Anthropic's AI Model",
    LLMProvider.gpt => "OpenAI's AI Model",
    LLMProvider.grok => "xAI's AI Model",
  };

  IconData _providerIcon(LLMProvider provider) => switch (provider) {
    LLMProvider.gemini => Icons.auto_awesome,
    LLMProvider.claude => Icons.psychology_outlined,
    LLMProvider.gpt => Icons.smart_toy_outlined,
    LLMProvider.grok => Icons.bolt_outlined,
  };

  @override
  Widget build(BuildContext context) {
    final colorScheme = Theme.of(context).colorScheme;

    return SafeArea(
      child: Scaffold(
        body: Column(
          children: [
            Expanded(
              child: _response.isEmpty
                  ? Center(
                      child: Column(
                        mainAxisSize: MainAxisSize.min,
                        children: [
                          Text(
                            'ANDROIDRIDE',
                            style: TextStyle(
                              fontWeight: FontWeight.bold,
                              fontSize: 35,
                            ),
                          ),
                          if (loading)
                            Column(
                              children: [
                                SizedBox(height: 8),
                                SpinKitThreeBounce(
                                  color: colorScheme.onSurface,
                                  size: 20,
                                ),
                              ],
                            ),
                        ],
                      ),
                    )
                  : SingleChildScrollView(
                      padding: const EdgeInsets.all(16),
                      child: MarkdownBody(
                        data: _response,
                        styleSheet: MarkdownStyleSheet(
                          p: TextStyle(
                            color: colorScheme.onSurface,
                            fontSize: 16,
                          ),
                          h1: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
                        ),
                        onTapLink: (text, href, title) {
                          if (href != null) {
                            launchUrl(Uri.parse(href));
                          }
                        },
                      ),
                    ),
            ),
            Container(
              padding: const EdgeInsets.fromLTRB(12, 8, 12, 12),
              decoration: BoxDecoration(
                color: colorScheme.surface,
                border: Border(
                  top: BorderSide(color: colorScheme.outlineVariant),
                ),
              ),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                mainAxisSize: MainAxisSize.min,
                children: [
                  ActionChip(
                    label: Text(_providerTitle(_selectedProvider)),
                    avatar: Icon(_providerIcon(_selectedProvider), size: 18),
                    onPressed: loading ? null : showProviderBottomSheet,
                  ),
                  const SizedBox(height: 8),
                  Material(
                    elevation: 1,
                    borderRadius: BorderRadius.circular(24),
                    color: colorScheme.surfaceContainerHighest,
                    child: Row(
                      crossAxisAlignment: CrossAxisAlignment.end,
                      children: [
                        Expanded(
                          child: TextField(
                            controller: _searchController,
                            minLines: 1,
                            maxLines: 4,
                            enabled: !loading,
                            textInputAction: TextInputAction.send,
                            onSubmitted: (_) => _ask(),
                            decoration: InputDecoration(
                              hintText: 'Ask something...',
                              border: InputBorder.none,
                              contentPadding: EdgeInsets.symmetric(
                                horizontal: 16,
                                vertical: 8,
                              ),
                            ),
                          ),
                        ),
                        Padding(
                          padding: const EdgeInsets.all(4.0),
                          child: IconButton.filled(
                            onPressed: loading ? null : _ask,
                            icon: const Icon(Icons.arrow_upward),
                          ),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  void showProviderBottomSheet() {
    showModalBottomSheet(
      context: context,
      showDragHandle: true,
      shape: const RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
      ),
      builder: (context) {
        return SafeArea(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Padding(
                padding: const EdgeInsets.fromLTRB(16, 8, 16, 4),
                child: Align(
                  alignment: Alignment.centerLeft,
                  child: Text(
                    "Choose Provider",
                    style: Theme.of(context).textTheme.titleMedium,
                  ),
                ),
              ),
              ...LLMProvider.values.map((provider) {
                final isSelected = provider == _selectedProvider;
                return ListTile(
                  leading: CircleAvatar(
                    child: Icon(
                      _providerIcon(provider),
                      size: 18,
                      color: Theme.of(context).colorScheme.onPrimaryContainer,
                    ),
                  ),
                  title: Text(_providerTitle(provider)),
                  subtitle: Text(_providerSubTitle(provider)),
                  trailing: isSelected
                      ? Icon(
                          Icons.check_circle,
                          color: Theme.of(context).colorScheme.primary,
                        )
                      : null,
                  onTap: () {
                    setState(() {
                      _selectedProvider = provider;
                      Navigator.pop(context);
                    });
                  },
                );
              }),
            ],
          ),
        );
      },
    );
  }

  void _ask() async {
    final prompt = _searchController.text.trim();

    if (prompt.isEmpty || loading) return;

    FocusManager.instance.primaryFocus?.unfocus();

    setState(() {
      loading = true;
      _response = '';
    });

    final response = switch (_selectedProvider) {
      LLMProvider.gemini => await LlmService.instance.generageGeminiContent(
        prompt,
      ),
      LLMProvider.claude => await LlmService.instance.generageClaudeContent(
        prompt,
      ),
      LLMProvider.gpt => await LlmService.instance.generageGPTContent(prompt),
      LLMProvider.grok => await LlmService.instance.generageGrokContent(prompt),
    };


    setState(() {
      loading = false;
      _response = response;
    });
  }
}


.env file
Please put your api keys here. If not, you can directly use your api keys in your code, but thats a bad practice.


GEMINI_API_KEY =“YOUR_API_KEY”
CLAUDE_API_KEY =“YOUR_API_KEY”
GPT_API_KEY =“YOUR_API_KEY”
GROK_API_KEY =“YOUR_API_KEY”

llm_provider.dart


enum LLMProvider { gemini, claude, gpt, grok }

Using an enum makes code easier to maintain, If more llms are added in future.

Before API Integration

  • You must obtain an API key from the AI provider (OpenAI, Google Gemini, Anthropic, xAI, etc.).
  • Most AI APIs use the POST method for sending requests.
  • Set the required headers and request body exactly as shown in the provider’s cURL example or official documentation.
  • Models, request formats, and response structures may change over time, and some models may be deprecated. Always check the HTTP status code before processing the response.
  • In a cURL command, -H specifies HTTP headers, while -d sends the request body (payload).
  • Use Postman (desktop app or web version) to test the API.

Gemini API Integration

Gemini API Key URL


  static const _geminiUrl =
      "https://generativelanguage.googleapis.com/v1/interactions";

 Future generageGeminiContent(String prompt) async {
    final geminiApiKey = dotenv.get('GEMINI_API_KEY');

    try {
      final response = await http.post(
        Uri.parse(_geminiUrl),
        headers: {
          'x-goog-api-key': geminiApiKey,
          'Content-Type': 'application/json',
        },
        body: jsonEncode({"model": "gemini-2.5-flash", "input": prompt}),
      );

      if (response.statusCode == 200) {
        final data = jsonDecode(response.body);
        return data['steps'][1]["content"][0]["text"];
      } else {
        return 'Error: ${response.statusCode} - ${response.body}';
      }
    } catch (e) {
      return 'Error: $e';
    }
  }


Use postman to API are working with needed headers and other fields.
gemini ai postman integration

Claude API Integration


Claude API Key URL
claude api integration in postman


  Future generageClaudeContent(String prompt) async {
    final claudeApiKey = dotenv.get('CLAUDE_API_KEY');

    try {
      final response = await http.post(
        Uri.parse(_claudeUrl),
        headers: {
          'x-api-key': claudeApiKey,
          'Content-Type': 'application/json',
          'anthropic-version': '2023-06-01',
        },
        body: jsonEncode({
          "model": "claude-haiku-4-5-20251001",
          "max_tokens": 10000,
          "messages": [
            {"role": "user", "content": prompt},
          ],
        }),
      );

      if (response.statusCode == 200) {
        final data = jsonDecode(response.body);
        return data['content'][0]["text"];
      } else {
        return 'Error: ${response.statusCode} - ${response.body}';
      }
    } catch (e) {
      return 'Error: $e';
    }
  }

GPT API Integration


GPT API Key URL
gpt api integration in postman


  static const _openAiUrl = "https://api.openai.com/v1/responses";

Future generageGPTContent(String prompt) async {
    final gptApiKey = dotenv.get('GPT_API_KEY');

    try {
      final response = await http.post(
        Uri.parse(_openAiUrl),
        headers: {
          'Authorization': 'Bearer $gptApiKey',
          'Content-Type': 'application/json',
        },
        body: jsonEncode({"model": "gpt-5.4", "input": prompt}),
      );

      if (response.statusCode == 200) {
        final data = jsonDecode(response.body);
        return data['output'][0]["content"][0]["text"];
      } else {
        return 'Error: ${response.statusCode} - ${response.body}';
      }
    } catch (e) {
      return 'Error: $e';
    }
  }

Grok API Integration

GROK API Key URL
grok api integration

  static const _grokUrl = "https://api.x.ai/v1/responses";

  Future generageGrokContent(String prompt) async {
    final grokApiKey = dotenv.get('GROK_API_KEY');

    try {
      final response = await http.post(
        Uri.parse(_grokUrl),
        headers: {
          'Authorization': 'Bearer $grokApiKey',
          'Content-Type': 'application/json',
        },
        body: jsonEncode(
          {
           "model": "grok-4.5", 
          "input": prompt
          }),
      );

      if (response.statusCode == 200) {
        final data = jsonDecode(response.body);
        return data['output'][1]["content"][0]["text"];
      } else {
        return 'Error: ${response.statusCode} - ${response.body}';
      }
    } catch (e) {
      return 'Error: $e';
    }
  }


llm_service.dart

All LLM API integration logic is handled in this file, making it easy to integrate additional LLMs in the future.


import 'dart:convert';

import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:http/http.dart' as http;

class LlmService {
  LlmService._();

  static final LlmService instance = LlmService._();

  static const _geminiUrl =
      "https://generativelanguage.googleapis.com/v1/interactions";

  static const _claudeUrl = "https://api.anthropic.com/v1/messages";

  static const _openAiUrl = "https://api.openai.com/v1/responses";

  static const _grokUrl = "https://api.x.ai/v1/responses";

  Future generageGrokContent(String prompt) async {
    final grokApiKey = dotenv.get('GROK_API_KEY');

    try {
      final response = await http.post(
        Uri.parse(_grokUrl),
        headers: {
          'Authorization': 'Bearer $grokApiKey',
          'Content-Type': 'application/json',
        },
        body: jsonEncode(
          {
           "model": "grok-4.5", 
          "input": prompt
          }),
      );

      if (response.statusCode == 200) {
        final data = jsonDecode(response.body);
        return data['output'][1]["content"][0]["text"];
      } else {
        return 'Error: ${response.statusCode} - ${response.body}';
      }
    } catch (e) {
      return 'Error: $e';
    }
  }

  Future generageGPTContent(String prompt) async {
    final gptApiKey = dotenv.get('GPT_API_KEY');

    try {
      final response = await http.post(
        Uri.parse(_openAiUrl),
        headers: {
          'Authorization': 'Bearer $gptApiKey',
          'Content-Type': 'application/json',
        },
        body: jsonEncode({"model": "gpt-5.4", "input": prompt}),
      );

      if (response.statusCode == 200) {
        final data = jsonDecode(response.body);
        return data['output'][0]["content"][0]["text"];
      } else {
        return 'Error: ${response.statusCode} - ${response.body}';
      }
    } catch (e) {
      return 'Error: $e';
    }
  }

  Future generageGeminiContent(String prompt) async {
    final geminiApiKey = dotenv.get('GEMINI_API_KEY');

    try {
      final response = await http.post(
        Uri.parse(_geminiUrl),
        headers: {
          'x-goog-api-key': geminiApiKey,
          'Content-Type': 'application/json',
        },
        body: jsonEncode({"model": "gemini-2.5-flash", "input": prompt}),
      );

      if (response.statusCode == 200) {
        final data = jsonDecode(response.body);
        return data['steps'][1]["content"][0]["text"];
      } else {
        return 'Error: ${response.statusCode} - ${response.body}';
      }
    } catch (e) {
      return 'Error: $e';
    }
  }

  Future generageClaudeContent(String prompt) async {
    final claudeApiKey = dotenv.get('CLAUDE_API_KEY');

    try {
      final response = await http.post(
        Uri.parse(_claudeUrl),
        headers: {
          'x-api-key': claudeApiKey,
          'Content-Type': 'application/json',
          'anthropic-version': '2023-06-01',
        },
        body: jsonEncode({
          "model": "claude-haiku-4-5-20251001",
          "max_tokens": 10000,
          "messages": [
            {"role": "user", "content": prompt},
          ],
        }),
      );

      if (response.statusCode == 200) {
        final data = jsonDecode(response.body);
        return data['content'][0]["text"];
      } else {
        return 'Error: ${response.statusCode} - ${response.body}';
      }
    } catch (e) {
      return 'Error: $e';
    }
  }
}


Thats all for now.

Flutter Gemini API Integration Tutorial 2026

In this tutorial, you will learn how to integrate Gemini AI into your Flutter app. We are going to use the Interactions API provided by Gemini.

Let’s start.

Okay… Let’s make a simple Flutter project named **`flutter_gemini_api`** using the `flutter create` command.

If you don’t know how to make a Flutter project read this command guide and Flutter Android Studio setup guide.

After that, open the project in your IDE and open `lib/main.dart`. Remove everything and create a MaterialApp template using `mateapp` (it only works if you are using the Awesome Flutter Snippets extension).

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() => runApp(GeminiApiIntegration());

class GeminiApiIntegration extends StatefulWidget {
  GeminiApiIntegration({super.key});

  @override
  State createState() => _GeminiApiIntegrationState();
}

class _GeminiApiIntegrationState extends State {
  final TextEditingController _searchController = TextEditingController();

  String _response = "";


  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Gemini Api Integration',
      debugShowCheckedModeBanner: false,
      home: Scaffold(
          appBar: AppBar(
            title: const Text('Gemini Api Integration'),
          ),
          body: SingleChildScrollView(
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                children: [
                  TextField(
                    decoration: InputDecoration(hintText: "Ask something..."),
                    controller: _searchController,
                  ),
                  const SizedBox(height: 16),
                  SizedBox(
                          width: double.infinity,
                          child: OutlinedButton(
                              onPressed: () async {},
                              child: Text("Ask"))),
                  const SizedBox(height: 16),
                  Text(_response),
                ],
              ),
            ),
          )),
    );
  }
}


This is the UI, and you can test it using an emulator. Check that everything works fine.

After that, we need to get a Gemini API key and work on the integration.

flutter gemini api

Documentation: Gemini AI Documentation

Go to this URL: Gemini AI Studio – API Keys.

Open the pubspec.yaml file and add the `http` package.

http: ^1.6.0

Now, add the API integration code.

 bool loading = false;

  static const _apiKey =
      "YOUR_API_KEY";

  static const _url =
      "https://generativelanguage.googleapis.com/v1/interactions";

  Future _generateContent(String prompt) async {
    try {
      final response = await http.post(Uri.parse(_url),
          headers: {
            'Content-Type': 'application/json',
            'x-goog-api-key': _apiKey,
          },
          body: jsonEncode({
            "model": "gemini-2.5-flash",
            "input": prompt,
          }));

      if (response.statusCode == 200) {
        final data = jsonDecode(response.body);
        return data['steps'][1]['content'][0]['text'];
      } else {
        return "Error: ${response.statusCode} - ${response.reasonPhrase}";
      }
    } catch (e) {
      print(e);
      return "Error: $e";
    }
  }

Here, you can see that I have used the Interactions API and sent a POST request with the `gemini-2.5-flash` model. You can also use the latest free-tier model.

Check the status code carefully, as it may be useful. The status code may change due to model overload or if the model has been deprecated.

It works only if the status code is `200`.

The most important parameters are the API key, model, and input.

You can also try it in Postman. It has a web version, so there’s no need to download or install it.

bool loading = false;


 loading
                      ? CircularProgressIndicator()
                      : SizedBox(
                          width: double.infinity,
                          child: OutlinedButton(
                              onPressed: () async {
                                setState(() {
                                  loading = true;
                                  _response = "";
                                });
            
                                final response = await _generateContent(
                                    _searchController.text);
            
                                setState(() {
                                  loading = false;
                                  _response = response;
                                });
                              },
                              child: Text("Ask"))),

  • You need to add a loading variable with an initial value of false.
  • Replace the button UI with the following code.
  • When the button is clicked, set the loading variable to true. This will rebuild the UI using setState and display a CircularProgressIndicator. Once the API integration is complete, set it back to false and rebuild the UI again using setState.

Full Source Code

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() => runApp(GeminiApiIntegration());

class GeminiApiIntegration extends StatefulWidget {
  GeminiApiIntegration({super.key});

  @override
  State createState() => _GeminiApiIntegrationState();
}

class _GeminiApiIntegrationState extends State {
  final TextEditingController _searchController = TextEditingController();

  String _response = "";
  bool loading = false;

  static const _apiKey =
      "YOUR_API_KEY";

  static const _url =
      "https://generativelanguage.googleapis.com/v1/interactions";

  Future _generateContent(String prompt) async {
    try {
      final response = await http.post(Uri.parse(_url),
          headers: {
            'Content-Type': 'application/json',
            'x-goog-api-key': _apiKey,
          },
          body: jsonEncode({
            "model": "gemini-2.5-flash",
            "input": prompt,
          }));

      if (response.statusCode == 200) {
        final data = jsonDecode(response.body);
        return data['steps'][1]['content'][0]['text'];
      } else {
        return "Error: ${response.statusCode} - ${response.reasonPhrase}";
      }
    } catch (e) {
      print(e);
      return "Error: $e";
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Gemini Api Integration',
      debugShowCheckedModeBanner: false,
      home: Scaffold(
          appBar: AppBar(
            title: const Text('Gemini Api Integration'),
          ),
          body: SingleChildScrollView(
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                children: [
                  TextField(
                    decoration: InputDecoration(hintText: "Ask something..."),
                    controller: _searchController,
                  ),
                  const SizedBox(height: 16),
                  loading
                      ? CircularProgressIndicator()
                      : SizedBox(
                          width: double.infinity,
                          child: OutlinedButton(
                              onPressed: () async {
                                setState(() {
                                  loading = true;
                                  _response = "";
                                });
            
                                final response = await _generateContent(
                                    _searchController.text);
            
                                setState(() {
                                  loading = false;
                                  _response = response;
                                });
                              },
                              child: Text("Ask"))),
                  const SizedBox(height: 16),
                  Text(_response),
                ],
              ),
            ),
          )),
    );
  }
}


What Is The Advantages Of Machine Learning?

ENIAC, the first computer, could perform 5,000 additions per second. In comparison, an average person can perform approximately 0.4 additions per second.

This means the first computer was about 12,500 times faster than a human.

Now, in today’s world, computers can perform billions of operations per second. When machines, particularly computers, learn to imitate human behavior—through machine learning—the potential is extraordinary.

They can outperform us in a matter of seconds.

So, let’s talk about the advantages of Machine Learning.

Automated decision without human intervention

One of the noted advantage is that, it does not need a human intervention for making decisions. It will think like human or based how the model is set it up, and make decisions.

It will do as same like human do and sometimes better, especially in repetitive, data-driven, or time-sensitive scenarios.

Personalization

Every client or user is treated as a top priority, No differentiation occurs—each user receives the same level of attention and care. Based on individual patterns, preferences, likes, and dislikes, machine learning models understand what to suggest and what to avoid.

For example, platforms like YouTube, Netflix, and Amazon use machine learning to power their recommendation systems. These systems treat each user uniquely, tailoring content or product suggestions to match personal behavior and interests.

24*7 availability

They are available whenever anyone needs them. No need for rest, sleep, or food like humans do. It means, whenever people need any help from deployed machine learning, it will be done.

Improvement Over Time

Just like experienced professionals improve with time, machine learning models also get better as they are exposed to more data. As the model continues to receive new data after deployment—even data it wasn’t originally trained or tested on—it can adapt, refine its predictions, and improve overall performance.

Using techniques like continuous learning or model retraining, the system becomes more accurate and efficient in handling real-world scenarios.

Handles Large Sets of Data

In this information age, there is a vast amount of data in various formats—text, audio, video, and more. It is very difficult for humans to handle all this data accurately because the chances of mistakes are high. However, machines operate using only 0s and 1s (binary), so they convert every type of data into this format to process and understand it efficiently. While humans converting data into binary might become complex as data volume grows, machines are built to handle and process large datasets quickly and accurately.

Less cost when compared to human

In the long term, its cost is considered to be less compared to humans, and machines can work 24*7. Once implemented, the ongoing costs—such as maintenance, updates, and electricity—are relatively low when spread over many years.

This makes machine learning a cost-effective solution for businesses looking to automate processes and improve efficiency.

New ML Job Opportunities

In the job zone, ML has come with new opportunities such as:

  • Data Scientist
  • ML Engineer
  • MLOps Engineer
  • Prompt Engineer

with competitive salary.

Beyond technical roles, ML is also creating opportunities in ethical AI, AI policy, and AI product management, reflecting the broader ecosystem around machine learning technologies.

Disadvantages of Machine Learning

Just like its advantages, Machine Learning also has disadvantages.

Let’s talk about that too.

Large Data Needed for Training

The model needs to be trained on a large number of high-quality datasets. If the dataset is small or of low quality, it may lead to poor or average predictions. The model also needs to be trained with new data regularly; otherwise, the ML model will degrade over time.

Resources for Training Are Expensive

For large ML models, training requires CPUs or GPUs. This is actually very expensive, both for renting and buying. They also need expensive infrastructure, as training produces heat that must be controlled to ensure better performance and to prevent overheating.

Job Loss

Due to automation of tasks and significantly better output in less time, there may be increased job losses in different sectors. Most companies have started using AI, which may result in job loss for new joiners or less experienced people. However, ML also creates new opportunities based on machine learning. Still, data typing and most basic tasks are expected to be handled by AI or machine learning in the future.

Manipulation with Malicious Data

Biased and malicious data can significantly alter decisions and lead to incorrect predictions. Manipulation can happen at any time—it may occur during fine-tuning with user feedback or from biased data collected during scraping or training in the machine learning model lifecycle.

Lack of Transparency in How Decisions Are Made (Black Box Problem)

It is often unclear how decisions are made based on the data. In some cases, it is difficult to understand why a particular outcome occurred. This lack of transparency makes debugging and improving predictions more challenging.

Reference:

Machine Learning Model Lifecycle – What I Understand

Everything undergoing creation or use will have lifecycle.

In Machine learning, There are 2 terms Machine learning lifecycle and machine learning model lifecycle.

They might seem quite similar at first glance, but actually, the Machine Learning Model Lifecycle is just a subset of the broader Machine Learning lifecycle.

In short,

  • Machine learning lifecycle: tells about the whole process. – managing entire mobile factory.
  • Machine Learning Model lifecycle: tells only about the machine learning model- managing one Mobile.

At first, I thought that Model Creation -> Model Training -> Model Deployment, these are the steps, but there’s much more to it.

Steps in the Machine learning model lifecycle:

machine learning model lifecycle
1. Model designing
2. Model training
3. Model Evaluation
4. Model deployment
5. Model monitoring
6. Model maintenance or retraining.
7. Model retirement

1. Model Designing

This is the first phase in model building. Here, we decide what type of algorithm to use, what kind of input and output the model should handle, and where the model will be deployed for user access.

In this phase, we can say that the model structure is completed.

2. Machine training

After creating the model, we need to train it. We already selected an appropriate algorithm and use it to help the machine, learn from the data by identifying patterns.

The data can come from your own sources such as your proprietary records (medical records, bank transactions) or publicly available repositories like Kaggle. This data is then fed to the model, and the model learns from it.

3. Model Evaluation

We need to test the model using unseen data to evaluate its performance. This testing data should not have been used during the training process. Evaluating with new data helps us understand how well the model generalizes to real-world scenarios.

If we skip this step, there’s a high chance the model may fail in production.

4. Model Deployment

To make the model available to users, we need to deploy it—usually by hosting it somewhere. This could be on a server, cloud, or even on edge devices like IoT hardware or mobile phones.

However, large models often can’t be deployed on low-resource devices due to hardware limitations. We can expose the model through an API, allowing users or other systems to access it over the internet.

5. Model monitoring

Still, we need to monitor the model because the model cannot be 100% right in every prediction. Still, many models in production are making mistakes.

ChatGpt, Gemini also says they can make mistakes, right? That’s why this phase is very important. As new data comes in, the model may make wrong decisions.

6. Model retraining

New data or queries can come in during monitoring, after deployment, or from other sources. When this happens, we need to retrain the model to improve its prediction accuracy.

This phase is important for continuously improving the model’s performance.

7. Model retirement

This is the final phase of a machine learning model’s lifecycle. Just like human beings—when we get older, we retire from our job or business.

Similarly, when a model becomes outdated or the data it was trained on is no longer effective, it may fail to make accurate decisions or predictions in the current situation.

In such cases, we need to retire the model to avoid unnecessary resource utilization.

References:

What Is Machine Learning? What I Learned.

Nowadays, we are surrounded by various AI and machine learning technologies, many of which we use directly or indirectly.

If you use Google or any other search engine to get here, that’s an example.

YouTube, Netflix, Amazon recommendations, self-learning cars, face lock on devices, autocorrect features, and many more.

However, everything started from a thought

Can machines think? —a question posed by Alan Turing, a British mathematician—sparked the beginning of artificial intelligence.

Later, Arthur Samuel, the father of machine learning, created the first machine learning program that taught itself by playing games.

Yes — that’s where it all began.

okay,  what is actually Machine Learning?

What Is Machine Learning?

what is machine learning?
Just like the name says, machines try to learn from data and make decisions. It’s actually like imitating us.

In our childhood, we learned to recognize cats or dogs by seeing them many times.

When we see them again, we understand whether it’s a cat or a dog. The images we saw are stored in our minds, and we compare new ones with our memory. Similarly, machines also learn from data and try to identify or predict things, just like we do.

In the real world, a good example is unlocking a mobile with face scanning. When your phone captures your face for the first time, it stores the image and extracts features like nose shape or jawline, converting them into numbers.

Later, when you try to unlock it, the phone compares your live face with the stored data. If the match is above a certain threshold, it unlocks the phone — saying, “It’s you.”

Machine learning flutter

So, what is data? Anything that carries enough information to help a machine learn and make decisions can be called data.

For example, this blog post could be part of the training data for a model that explains machine learning. But to train a good model, you’d need many such blog posts — from different sources.

Similarly, articles, tweets, photos, audio clips, videos, medical records, and transactions — all of these are forms of data. If they carry useful information, they can be used in machine learning.

Based on the type of data and the way we train the model, machine learning is generally classified into four categories.
machine learning types

  1. Supervised Machine Learning
  2. Unsupervised Learning
  3. Semi – Supervised
  4. Reinforcement

Supervised Machine Learning

supervised machine learning labeled data

A machine learns from labeled data to make predictions — this is called Supervised Machine Learning.
Labeled data means the data comes with a label or description.

For example, you might provide a dog image labeled “dog” or an audio clip labeled “music.”
This helps the machine understand what the data represents.

An example algorithm used in supervised learning is Linear Regression, which can be used to predict house prices.

Unsupervised Learning

This is the opposite of Supervised Learning. It uses only data — no labels or descriptions.
Here, we don’t tell the machine what to learn.

So, the machine needs to find patterns or groups within the data on its own.
For example, K-Means Clustering is an algorithm used for customer segmentation (grouping users based on behavior for targeted marketing).

Semi-Supervised Machine Learning

In this type, the machine is trained with a small amount of labeled data and a large amount of unlabeled data.

Semi-Supervised Machine Learning = Low volume of labeled data + Large volume of unlabeled data.
Example: Self-training + Logistic Regression

Reinforcement Learning

It’s like training a dog. If you tell it to sit and it does, you give it a cookie. Otherwise, no cookie. Just like that, a machine learns by trial and error to reach a specific goal — by getting rewards or not getting them.

eg: Self driving car uses reinforcement learning to improve their driving skills.

That’s all for today.
will be back.

5 Ways To Get Instance Of ViewModel In Android

Creating viewmodel instance is needed if you are using MVVM architecture. Otherwise, MVVM is incomplete.  So in this post, I will tell you about different ways for creating instance of ViewModel in activity or fragment.

By using correct usage of viewmodel, we can avoid ‘Android cannot create instance of viewmodel’ issue also.

okay, let’s start.

First I will tell you in short. So it will save your time.

  1. viewModel() – Used in Jetpack Compose.
  2. by viewModels() – Apply it with Activity or fragment.
  3. by activityViewModels() – Recommended to use when multiple fragments need to share same data and has same instance of Activity
  4. Using ViewModelProvider – old way but still works.
  5. using ViewModelProvider.Factory –  Make use of it if you need to pass arguments to viewmodel.
  6. @hiltViewModel – This is from Hilt dependency injection, very helpful if you are using Hilt.
    • hiltViewModel() – Use it in Composables

Below, I have mentioned versions of different dependencies, so if you need different version, please check the maven repository and use the latest one.

Detailed Version With Code

  1. viewModel()

In Jetpack Compose, viewModel() delegate can be used for creating instance of viewmodel. But before that you need to include the dependencies in gradle file, only then you can use this. If you want to pass parameters, then go with Hilt or ViewModelProvider.Factory


androidx.Lifecycle:lifecycle-viewmodel-compose:"latest version"(2.8.7 )

2.  by viewModels()

val viewModel: MyViewModel by viewModels()

If you are using XML based approach, you can easily create instance of viewmodel using viewModels(). This does not support passing parameters.

3. activityViewModels()

val viewModel: MyViewModel by activityViewModels()

You can use this in your fragment to access instance of viewmodel – better to use when multiple fragment within same activity needs viewmodel instance.

implementation("androidx.fragment:fragment-ktx:1.6.2")

4. Use ViewModelProvider

ViewModelProvider(this).get(MyViewModel::class.java)

As the name suggests ViewModelProvider gives the viewmodel instance by passing class type of the viewmodel.
5. ViewModelProvider.Factory

val repository = MyRepository()
val factory = MyViewModelFactory(repository)
viewmodel = ViewModelProvider(this, factory)[MyViewModel::class.java]

In most cases, we need to pass a repository or other instances to the viewmodel. So in this example, i am passing a repository to the viewmodel using ViewModelProvider.

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider

class MyViewModelFactory(private val repository: MyRepository) :ViewModelProvider.Factory{
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if(modelClass.isAssignableFrom(MyViewModel::class.java)){
         return MyViewModel(repository) as T
        }
        throw IllegalArgumentException("Unknown ViewModel class")
    }

}

Here, the subclass of ViewModelProvider.Factory will receives the instance of repository and creates viewmodel instance for you.

5. Using @HiltViewModel

This approach can be used if you are Hilt dependency injection in your project. But you need to do some changes here, not easy like above steps,

1. You need to add dependency in gradle file.
2. Define subclass of Application and annotate with @HiltAndroidApp.
3. Annotate viewmodel class with @HiltViewModel.
4. if you want to access instance of viewmodel in activity or fragment, annotate those with @AndroidEntryPoint


  //  id 'com.google.dagger.hilt.android' version '2.48' apply false

   // Dagger - Hilt
    implementation "com.google.dagger:hilt-android:2.48"
    kapt "com.google.dagger:hilt-android-compiler:2.48"
    kapt "androidx.hilt:hilt-compiler:1.0.0"

  • These are the dependencies you need to add in gradle file.
@HiltAndroidApp
class MyApplication: Application()
  • Create a subclass of Application like above and Use @HiltAndroidApp annotation.

@HiltViewModel
class MainViewModel @Inject constructor(
    private val repository: Repository,
    application: MyApplication
): AndroidViewModel(application)

  • If there is any parameters need to pass, create a module and pass from there. So internally hilt will manage. Use @Inject to injecting dependencies through constructor.
@AndroidEntryPoint
class MyFragment : Fragment() {

    private val mainViewModel: MainViewModel by viewModels()


hiltViewModel() - call in composable if you are using hilt.

  • Access like above your viewmodel, do not forget to annotate activity or fragment with @AndroidEntryPoint where you are calling viewmodel.

This is for now, may be I will update later.
Thanks.

Color.kt vs colors.xml : Which is better for Jetpack Compose?

From the arrival of Jetpack Compose, developer can store and access color values in 2 ways.

i. Color.kt – Modern approach.
ii. XML – Traditional approach.

both methods are acceptable in jetpack compose.

but which one is better ?

It actually depends on the scenario.

i. For New Jetpack Compose projects

Here, Its better to store colors in Color.kt because its more efficient than resolving xml resources by id. Additionally it provides type safety and IDE can automatically suggest colors.

Using this approach, we can assign different color for light and dark theme. If you are making a multiplatform project. recommended to use this approach.

color kt file location
You can easily found the file in app/src/main/java/com/userName/packagename/ui/theme/Color.kt

val Colors.topAppBarBackgroundColor:  Color
    @Composable
    get() = if(isSystemInDarkTheme()) Color.Black else Color.White
  • Above code, just implementing dark and light theme based on the isSystemInDarkTheme() method.

ii. Projects with XML + Jetpack compose

In this situation, you can store color values in colors.xml file. So both views and composable can easily access from a central location.

For implementing Dark theme, you can make use of night mode of colors.xml.
colors.xml file location
Colors.xml file can be found in app/src/main/res/values/colors.xml.

val appBarBackgroundColor = colorResource(R.color.topAppBarBackgroundColor)

Summmary

Users can still store color values in both files. However for Jetpack Compose, the preferred approach is storing color values in Color.kt file which make IDE to suggest and assures type safety. But if you are working on a project with both Jetpack Compose and XML, you can store values in colors.xml as the traditional way.

Whatever it is, try to store all color code in one file for easy access and maintenance.

3 Things You Must Need To Know : Jetpack Compose vs XML

Let’s compare Jetpack Compose vs XML.

Before that I need to tell you something.

When Kotlin arrived, I searched the whole internet to decide: should I learn Kotlin or not?
Back then, some posts convinced me that learning Kotlin would give me a strong advantage as an Android developer.

And they were right.

Now, for anyone searching the internet and wondering whether to learn Jetpack Compose, here’s my answer:

1. Performance

live edit in jetpack compose vs XML
I will start with my favorite part about Jetpack Compose – Live edit.

Why its my favorite?

This one really improved my productivity.

If you did a simple layout change,You dont need to restart the whole application, it just reloads direct in your emulator or real devices. But it still evolving, so sometimes it faces issues with larger projects.

XML lacks this feature due to its imperative UI paradigm and Android view system.

Language Support

Jetpack compose is defined for Kotlin, so if you want to use compose, then you need to write kotlin code. As for now, i don’t seem any plan with java.

XML works well with both languages, so choice of language is not a barrier.

Rendering layouts

When it comes to rendering layouts, XML perform better for static layouts, but less efficient for complex layouts, because findViewById and binding calls make difficult. But compose is better for complex UIs because it only renders the specific part of the UI that needs to be changed.

Resource Utilization

XML need more effort when rendering complex layout ui. on the other side Jetpack Compose efficiently manage recompositions, but triggering unnecessary recomposition can lead to inefficiencies.

Preview support

Both have built in preview support in Android Studio.
In XML, android studio – we can easily drag and drop our views wherever we want. it make begginer effort less. So that doesn’t end here. Compose also has a nice preview system, renders preview more efficiently than XML.

2. Support

Community

Jetpack Compose is relatively new, but its community is growing day by day. But for XML, already a huge community out there. as you know, it comes from api level 1. So most of the code are around with XML. Most of the questions you will face will already answered by someone in stackoverflow, reddit or any other site in google. compose not in a long way, they also quickly closing the gap.

Devices Support

Jetpack Compose will not work below api level 21(lolipop), to support running below api 21, then you need to use XML. But however, number of running devices below api 21 is very low. If you are migrating from XML to Compose. sometimes, it might be difficult for larger projects. so you can go for a hybrid approach. such as using compose code in xml.

Using composeView, you can add compose inside XML.

3. Learning curve

i. Beginner

If you are new to android, learning curve for Jetpack Compose feels natural. if you are a beginner in android, but also familiar with Flutter or react native, then it will reduce the intensity of your learning process. However XML may seem easy at first glance, you can quickly understand each tag and its properties, how they work.

XML has abundance of tutorials, books and documentation, making it sufficient for beginner to learn. For compose, developers are creating more tutorials and vlogs, which increases the resources and reduce the gap.

ii. Already an android developer

It become moderate to you, if you are learning compose and also compose not that much documentation or abundance of tutorials out there. So sometimes, you may need to troubleshoot some issues your self.

for XML, there is lot of boilerplate code, also need to switch to java/kotlin code for functionality. For compose, that you can simply do in Kotlin, but must have a better knowledge in Kotlin.

Jetpack Compose vs XML – Summary

Its better to start learning Jetpack compose because that’s the future and it also make app development more productive. But still XML is in the ground, so you can not avoid that. if you are a beginner, most of the projects are in xml, its better to learn xml first, but many companies also hire people with compose due to new projects. so final part, its beneficial to know the basics of xml and go for Jetpack Compose.