Http request using Angular and capacitor for android does not work

Hi everyone.
I am not using ionic, just Angular with capacitor. My app will work just in android mobile. Now I have problem with the http requests, it does not work in mobile, but before it was working. I did not change anything about the requests. If I open my app in browser, it works, but for android as apk no, there is not error messages, I use url with parameters and on LogCat, it puts stranger caracters I do not know why. I tried to do the same request in the same endpoint in a new app test and the messege error is that Cor is blocking it. in my manifest I put all permission for internet, the config security as well with the server ip. android:usesCleartextTraffic=“true”. On server is SAP and I can not change the header response for Cors. I use fake api rest it works. What is the problem? I am using capacitor 6, Angular 18, HttpClient for requests, min SDK 23 , Compiled SDK 34, Java 11 with gradle. I tried to recompiled a old version that it was working, and after generate the apk it does not work. Please what sould I do? I just do not want to use ionic because I would have to change a lot things in my app.

Any help will be welcome.

Regards

If you cannot change or don’t have control of the CORS headers on the web server you are making calls to, then you’ll need to use the CapacitorHttp plugin.

You can just enable it and not change anything else as it will intercept fetch and XMLHttpRequest requests. Or, you can use the plugin methods directly.

Http request (GET) does not work in android. I am using HttpClient from angular. I tried with CapacitorHttp and is the same.
I put here all the important point. I tried to generate again android folder with capacitor but no way.

my this.servidor = ‘http://192.168.100.222:8000/

My component

  iniciarSesion(): void {
    var usuario = this.formLogin.value.usuario;
    var contrasena = this.formLogin.value.contrasena;
    var hash = this.generar_hash(contrasena);
    console.log("Hash");
    console.log(hash);
    
    this.loginService.getUsuario( usuario, hash ).subscribe({
      next: (data) => {
        // Guarda los datos en Storage para poder hacer peticiones
        localStorage.setItem('usuario', data.d.results[0].Usuario);
        localStorage.setItem('werks', data.d.results[0].Werks);
        localStorage.setItem('vkorg', data.d.results[0].Vkorg);
        localStorage.setItem('identificador', data.d.results[0].Identificador);

        // Redireccionar a la página principal
        this.logueado = true;
        this.router.navigate(['/julia/busqueda'], { queryParams: { id: 0 } });
      },
      error: (error: any) => {
        console.error(error);
      }
    }); 
  
  }

My service

getUsuario(usuario: string, contrasena: string): Observable<any> {
    const filter = `Usuario eq '${usuario}' and Password_MD5 eq '${contrasena}'`;
    const url = `ZSD_APP_INTERLOCUTORES_SRV/InterlocutorDataSet?$filter=${filter}`;
    return this.httpSap.get(url);
}

My request services

import { HttpHeaders, HttpClient, HttpErrorResponse, HttpResponse } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, catchError, throwError, map, of, switchMap } from 'rxjs';
import { App } from '../config/app';
import { CapacitorHttp } from '@capacitor/core';


@Injectable({
  providedIn: 'root'
})
export class TokenService {
  private user = App.user;
  private pass = App.pass;
  private serverWeb = App.serverWeb;
  private serverAndroid = App.serverAndroid;
  private servidor: string;
  private token: any;
  private cookie: any;
  private base64Auth;
  
  constructor( 
    private http: HttpClient,
  ) { 
    this.token = "";
    this.cookie = "";
    this.servidor = this.serverAndroid;
    this.base64Auth = btoa(`${this.user}:${this.pass}`);
  }
  

  // Genera el header ya con el token 
  getHeader(): Observable<any> {
    return this.getToken().pipe(
      map(token => {
        let httpHeaders = {
          'X-Csrf-Token': token,
          'Accept': 'application/json',
          'Content-Type': 'application/json',
          'X-Content-Type-Options': 'no-sniff',
          'Authorization': 'Basic ' + this.base64Auth
        };
        return httpHeaders;
      }),
      catchError(error => {
        console.error('Error fetching token', error);
        return of({});
      })
    );
  }

  getToken(): Observable<string> {
    const headers = new HttpHeaders({
      'Authorization': `Basic ${this.base64Auth}`,
      'X-Csrf-Token': 'Fetch',
      'Accept': 'application/json'
    });
    let url = this.servidor + "ZTOKEN_SRV/TOKENSet";
    return this.http.get(url, { headers, observe: 'response' }).pipe(
      map((response: HttpResponse<any>) => {
        this.token = response.headers.get('X-Csrf-Token') || '';
        this.cookie = response.headers.get('set-cookie') || '';
        return this.token;
      })
    );
  }
  
  get<T>(url: string): Observable<T> {
    const headers = new HttpHeaders({
      'X-Csrf-Token': 'Fetch',
      'Accept': 'application/json',
      'Content-Type': 'application/json',
      'Authorization': 'Basic ' + this.base64Auth
    });
    return this.http.get<T>(this.servidor + url, { headers }).pipe(
      catchError(this.handleError)
    )
  }

  post<T>(url: string, body: any): Observable<T> {
    return this.getHeader().pipe(
      switchMap(headers => {
        return this.http.post<T>(this.servidor + url, body, { headers }).pipe(
          catchError(this.handleError)
        )
      })
    );
  }
 
  put<T>(url: string, body: any): Observable<T> {
    return this.getHeader().pipe(
      switchMap(headers => {
        return this.http.put<T>(this.servidor + url, body, { headers }).pipe(
          catchError(this.handleError)
        )
      })
    );
  }
 
  delete<T>(url: string): Observable<T> {
    return this.getHeader().pipe(
      switchMap(headers => {
        return this.http.delete<T>(this.servidor + url, { headers }).pipe(
          catchError(this.handleError)
        )
      })
    );
  }



  // Manejo de errores
  private handleError(error: HttpErrorResponse): Observable<never> {
    console.error('An error occurred:', error);

    let errorMessage = 'An unknown error occurred!';
    if (error.error instanceof ErrorEvent) {
      // Error del lado del cliente
      errorMessage = `Error: ${error.error.message}`;
    } else {
      // Error del lado del servidor
      errorMessage = `Error ${error.status}: ${error.message}`;
      if (error.error && error.error.error && error.error.error.message) {
        errorMessage = `Error ${error.status}: ${error.error.error.message.value}`;
      } else {
        // Si no se encuentra el mensaje específico, muestra toda la estructura de error para depuración
        console.log('Complete server error response:', error.error);
      }
    }
    return throwError(() => errorMessage);
  }



  limpia_local_storage(): void {
    sessionStorage.removeItem('cliente');
    sessionStorage.removeItem('textoDigitado');
    sessionStorage.removeItem('prod');
    sessionStorage.removeItem('primera_busqueda');
    sessionStorage.removeItem('textoDigitado');
    sessionStorage.removeItem('cabinas');
    sessionStorage.removeItem('fechacab'); 
  }
}

My capacitor.config.ts

import type { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  appId: 'com.jma',
  appName: 'Julia Mobile',
  webDir: 'dist/app',
  plugins: {
    "CapacitorHttp": {
      "enabled": true,
    },
  }
};

export default config;

In Android studio
My AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        android:usesCleartextTraffic="true">

        <activity
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
            android:name=".MainActivity"
            android:label="@string/title_activity_main"
            android:theme="@style/AppTheme.NoActionBarLaunch"
            android:launchMode="singleTask"
            android:exported="true">

          <!-- Inicio de la app -->
          <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
          </intent-filter>


          <!-- Deep Link Intent Filter -->
          <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="jma" android:host="comunicacio" />
          </intent-filter>

        </activity>

        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"></meta-data>
        </provider>
    </application>

    <!-- Permissions -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera" android:required="false" />

</manifest>

My gradle variables

ext {
    minSdkVersion = 26
    compileSdkVersion = 34
    targetSdkVersion = 34
    androidxActivityVersion = '1.8.0'
    androidxAppCompatVersion = '1.6.1'
    androidxCoordinatorLayoutVersion = '1.2.0'
    androidxCoreVersion = '1.12.0'
    androidxFragmentVersion = '1.6.2'
    coreSplashScreenVersion = '1.0.1'
    androidxWebkitVersion = '1.9.0'
    junitVersion = '4.13.2'
    androidxJunitVersion = '1.1.5'
    androidxEspressoCoreVersion = '3.5.1'
    cordovaAndroidVersion = '10.1.1'
}

My MainActivity.java

package com.jma;

import com.getcapacitor.BridgeActivity;

public class MainActivity extends BridgeActivity {}

Please update your last post with proper code blocks so we can easily read it.

Hi, the problems continues. I create a webservice local in https://192.168.100.44:3000 with certificate ssl and in postman and in browser it works, but in my android app generated by capacitor is not work, it does not give me errors messages but the http request does not arrive in my webservice, why? in my webserver I am permitting cors with * . I think is something in capacitor. I can use CapacitorHttp or HttpClient or Axios the problem is the same. how can I fix that?

Thanks

Where are you checking for error messages? Are you looking in LogCat in Android Studio and in DevTools in Chrome by going to chrome://inspect/#devices and finding your device?

A guess is that Android is not trusting your self-signed SSL cert.

in chrome the messege errors is net::ERR_CONNECTION_REFUSED, but in android studio in LogCat there is not errror messege. This is my webservice in flask (Python) running: PS D:\Flask> python app.py

Ahh. That error is probably because you need to open up the port in your computer’s firewall to allow access from the Android device.

My firewall is disabled, but I opened the port tpc and udp in windows firewall as well. In android i put this code to trust in my ssl cert:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="false">
  <trust-anchors>
    <certificates src="@raw/certificate"/>
    <certificates src="system"/>
  </trust-anchors>
</base-config>
</network-security-config>

inside the raw folder I put my certificate.crt

This is my manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- Permissions -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera" android:required="false" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme"
        android:usesCleartextTraffic="true"
        android:networkSecurityConfig="@xml/network_security_config">

        <activity
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode"
            android:name=".MainActivity"
            android:label="@string/title_activity_main"
            android:theme="@style/AppTheme.NoActionBarLaunch"
            android:launchMode="singleTask"
            android:exported="true">

          <!-- Inicio de la app -->
          <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
          </intent-filter>


          <!-- Deep Link Intent Filter -->
          <intent-filter>
            <action android:name="android.intent.action.VIEW" />
            <category android:name="android.intent.category.DEFAULT" />
            <category android:name="android.intent.category.BROWSABLE" />
            <data android:scheme="jma" android:host="comunicacio" />
          </intent-filter>

        </activity>

        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"></meta-data>
        </provider>
    </application>

</manifest>

I am doing all local