Files
Backend-Api/ANDROID_SIGNALR_GUIDE.md

22 KiB

راهنمای اتصال اپلیکیشن Android به SignalR برای Face Embedding

1. افزودن کتابخانه SignalR به پروژه Android

در فایل build.gradle (Module: app) خود، dependency زیر را اضافه کنید:

dependencies {
    // SignalR for Android
    implementation 'com.microsoft.signalr:signalr:7.0.0'
    
    // اگر از Kotlin استفاده می‌کنید:
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1'
    
    // برای JSON پردازش:
    implementation 'com.google.code.gson:gson:2.10.1'
}

2. اضافه کردن Permission در AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

3. کد Java/Kotlin برای اتصال به SignalR

نسخه Java:

import com.microsoft.signalr.HubConnection;
import com.microsoft.signalr.HubConnectionBuilder;
import com.microsoft.signalr.HubConnectionState;
import com.google.gson.JsonObject;
import android.util.Log;

public class FaceEmbeddingSignalRClient {
    private static final String TAG = "FaceEmbeddingHub";
    private HubConnection hubConnection;
    private String serverUrl = "http://YOUR_SERVER_IP:PORT/trackingFaceEmbeddingHub"; // آدرس سرور خود را وارد کنید
    private long workshopId;

    public FaceEmbeddingSignalRClient(long workshopId) {
        this.workshopId = workshopId;
        initializeSignalR();
    }

    private void initializeSignalR() {
        // ایجاد اتصال SignalR
        hubConnection = HubConnectionBuilder
                .create(serverUrl)
                .build();

        // دریافت رویداد ایجاد Embedding
        hubConnection.on("EmbeddingCreated", (data) -> {
            JsonObject jsonData = (JsonObject) data;
            long employeeId = jsonData.get("employeeId").getAsLong();
            String employeeFullName = jsonData.get("employeeFullName").getAsString();
            String timestamp = jsonData.get("timestamp").getAsString();
            
            Log.d(TAG, "Embedding Created - Employee: " + employeeFullName + " (ID: " + employeeId + ")");
            
            // اینجا می‌توانید داده‌های جدید را از سرور بگیرید یا UI را بروزرسانی کنید
            onEmbeddingCreated(employeeId, employeeFullName, timestamp);
            
        }, JsonObject.class);

        // دریافت رویداد حذف Embedding
        hubConnection.on("EmbeddingDeleted", (data) -> {
            JsonObject jsonData = (JsonObject) data;
            long employeeId = jsonData.get("employeeId").getAsLong();
            String timestamp = jsonData.get("timestamp").getAsString();
            
            Log.d(TAG, "Embedding Deleted - Employee ID: " + employeeId);
            onEmbeddingDeleted(employeeId, timestamp);
            
        }, JsonObject.class);

        // دریافت رویداد بهبود Embedding
        hubConnection.on("EmbeddingRefined", (data) -> {
            JsonObject jsonData = (JsonObject) data;
            long employeeId = jsonData.get("employeeId").getAsLong();
            String timestamp = jsonData.get("timestamp").getAsString();
            
            Log.d(TAG, "Embedding Refined - Employee ID: " + employeeId);
            onEmbeddingRefined(employeeId, timestamp);
            
        }, JsonObject.class);
    }

    public void connect() {
        if (hubConnection.getConnectionState() == HubConnectionState.DISCONNECTED) {
            hubConnection.start()
                .doOnComplete(() -> {
                    Log.d(TAG, "Connected to SignalR Hub");
                    joinWorkshopGroup();
                })
                .doOnError(error -> {
                    Log.e(TAG, "Error connecting to SignalR: " + error.getMessage());
                })
                .subscribe();
        }
    }

    private void joinWorkshopGroup() {
        // عضویت در گروه مخصوص این کارگاه
        hubConnection.send("JoinWorkshopGroup", workshopId);
        Log.d(TAG, "Joined workshop group: " + workshopId);
    }

    public void disconnect() {
        if (hubConnection.getConnectionState() == HubConnectionState.CONNECTED) {
            // خروج از گروه
            hubConnection.send("LeaveWorkshopGroup", workshopId);
            
            hubConnection.stop();
            Log.d(TAG, "Disconnected from SignalR Hub");
        }
    }

    // این متدها را در Activity/Fragment خود override کنید
    protected void onEmbeddingCreated(long employeeId, String employeeFullName, String timestamp) {
        // اینجا UI را بروزرسانی کنید یا داده جدید را بگیرید
    }

    protected void onEmbeddingDeleted(long employeeId, String timestamp) {
        // اینجا UI را بروزرسانی کنید
    }

    protected void onEmbeddingRefined(long employeeId, String timestamp) {
        // اینجا UI را بروزرسانی کنید
    }
}

نسخه Kotlin:

import com.microsoft.signalr.HubConnection
import com.microsoft.signalr.HubConnectionBuilder
import com.microsoft.signalr.HubConnectionState
import com.google.gson.JsonObject
import android.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

class FaceEmbeddingSignalRClient(private val workshopId: Long) {
    
    companion object {
        private const val TAG = "FaceEmbeddingHub"
    }
    
    private lateinit var hubConnection: HubConnection
    private val serverUrl = "http://YOUR_SERVER_IP:PORT/trackingFaceEmbeddingHub" // آدرس سرور خود را وارد کنید
    
    init {
        initializeSignalR()
    }
    
    private fun initializeSignalR() {
        hubConnection = HubConnectionBuilder
            .create(serverUrl)
            .build()
            
        // دریافت رویداد ایجاد Embedding
        hubConnection.on("EmbeddingCreated", { data: JsonObject ->
            val employeeId = data.get("employeeId").asLong
            val employeeFullName = data.get("employeeFullName").asString
            val timestamp = data.get("timestamp").asString
            
            Log.d(TAG, "Embedding Created - Employee: $employeeFullName (ID: $employeeId)")
            onEmbeddingCreated(employeeId, employeeFullName, timestamp)
        }, JsonObject::class.java)
        
        // دریافت رویداد حذف Embedding
        hubConnection.on("EmbeddingDeleted", { data: JsonObject ->
            val employeeId = data.get("employeeId").asLong
            val timestamp = data.get("timestamp").asString
            
            Log.d(TAG, "Embedding Deleted - Employee ID: $employeeId")
            onEmbeddingDeleted(employeeId, timestamp)
        }, JsonObject::class.java)
        
        // دریافت رویداد بهبود Embedding
        hubConnection.on("EmbeddingRefined", { data: JsonObject ->
            val employeeId = data.get("employeeId").asLong
            val timestamp = data.get("timestamp").asString
            
            Log.d(TAG, "Embedding Refined - Employee ID: $employeeId")
            onEmbeddingRefined(employeeId, timestamp)
        }, JsonObject::class.java)
    }
    
    fun connect() {
        if (hubConnection.connectionState == HubConnectionState.DISCONNECTED) {
            CoroutineScope(Dispatchers.IO).launch {
                try {
                    hubConnection.start().blockingAwait()
                    Log.d(TAG, "Connected to SignalR Hub")
                    joinWorkshopGroup()
                } catch (e: Exception) {
                    Log.e(TAG, "Error connecting to SignalR: ${e.message}")
                }
            }
        }
    }
    
    private fun joinWorkshopGroup() {
        hubConnection.send("JoinWorkshopGroup", workshopId)
        Log.d(TAG, "Joined workshop group: $workshopId")
    }
    
    fun disconnect() {
        if (hubConnection.connectionState == HubConnectionState.CONNECTED) {
            hubConnection.send("LeaveWorkshopGroup", workshopId)
            hubConnection.stop()
            Log.d(TAG, "Disconnected from SignalR Hub")
        }
    }
    
    // این متدها را override کنید
    open fun onEmbeddingCreated(employeeId: Long, employeeFullName: String, timestamp: String) {
        // اینجا UI را بروزرسانی کنید یا داده جدید را بگیرید
    }
    
    open fun onEmbeddingDeleted(employeeId: Long, timestamp: String) {
        // اینجا UI را بروزرسانی کنید
    }
    
    open fun onEmbeddingRefined(employeeId: Long, timestamp: String) {
        // اینجا UI را بروزرسانی کنید
    }
}

4. استفاده در Activity یا Fragment

مثال با Login و دریافت WorkshopId

Java:

public class LoginActivity extends AppCompatActivity {
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        
        Button btnLogin = findViewById(R.id.btnLogin);
        btnLogin.setOnClickListener(v -> performLogin());
    }
    
    private void performLogin() {
        // فراخوانی API لاگین
        // فرض کنید response شامل workshopId است
        
        // مثال ساده (باید از Retrofit یا کتابخانه مشابه استفاده کنید):
        // LoginResponse response = apiService.login(username, password);
        // long workshopId = response.getWorkshopId();
        
        long workshopId = 123; // این را از response دریافت کنید
        
        // ذخیره workshopId
        SharedPreferences prefs = getSharedPreferences("AppPrefs", MODE_PRIVATE);
        prefs.edit().putLong("workshopId", workshopId).apply();
        
        // رفتن به صفحه اصلی
        Intent intent = new Intent(this, MainActivity.class);
        startActivity(intent);
        finish();
    }
}

public class MainActivity extends AppCompatActivity {
    private FaceEmbeddingSignalRClient signalRClient;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        // دریافت workshopId از SharedPreferences
        SharedPreferences prefs = getSharedPreferences("AppPrefs", MODE_PRIVATE);
        long workshopId = prefs.getLong("workshopId", 0);
        
        if (workshopId == 0) {
            // اگر workshopId وجود نداره، برگرد به صفحه لاگین
            Intent intent = new Intent(this, LoginActivity.class);
            startActivity(intent);
            finish();
            return;
        }
        
        // ایجاد و اتصال SignalR
        signalRClient = new FaceEmbeddingSignalRClient(workshopId) {
            @Override
            protected void onEmbeddingCreated(long employeeId, String employeeFullName, String timestamp) {
                runOnUiThread(() -> {
                    // بروزرسانی UI
                    Toast.makeText(MainActivity.this, 
                        "Embedding ایجاد شد برای: " + employeeFullName, 
                        Toast.LENGTH_SHORT).show();
                    
                    // دریافت داده‌های جدید از API
                    refreshEmployeeList();
                });
            }
            
            @Override
            protected void onEmbeddingDeleted(long employeeId, String timestamp) {
                runOnUiThread(() -> {
                    // بروزرسانی UI
                    refreshEmployeeList();
                });
            }
            
            @Override
            protected void onEmbeddingRefined(long employeeId, String timestamp) {
                runOnUiThread(() -> {
                    // بروزرسانی UI
                    refreshEmployeeList();
                });
            }
        };
        
        signalRClient.connect();
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (signalRClient != null) {
            signalRClient.disconnect();
        }
    }
    
    private void refreshEmployeeList() {
        // دریافت لیست جدید کارمندان از API
    }
}

Kotlin:

class LoginActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
        
        val btnLogin = findViewById<Button>(R.id.btnLogin)
        btnLogin.setOnClickListener { performLogin() }
    }
    
    private fun performLogin() {
        // فراخوانی API لاگین
        // فرض کنید response شامل workshopId است
        
        // مثال ساده (باید از Retrofit یا کتابخانه مشابه استفاده کنید):
        // val response = apiService.login(username, password)
        // val workshopId = response.workshopId
        
        val workshopId = 123L // این را از response دریافت کنید
        
        // ذخیره workshopId
        val prefs = getSharedPreferences("AppPrefs", Context.MODE_PRIVATE)
        prefs.edit().putLong("workshopId", workshopId).apply()
        
        // رفتن به صفحه اصلی
        val intent = Intent(this, MainActivity::class.java)
        startActivity(intent)
        finish()
    }
}

class MainActivity : AppCompatActivity() {
    private lateinit var signalRClient: FaceEmbeddingSignalRClient
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        // دریافت workshopId از SharedPreferences
        val prefs = getSharedPreferences("AppPrefs", Context.MODE_PRIVATE)
        val workshopId = prefs.getLong("workshopId", 0L)
        
        if (workshopId == 0L) {
            // اگر workshopId وجود نداره، برگرد به صفحه لاگین
            val intent = Intent(this, LoginActivity::class.java)
            startActivity(intent)
            finish()
            return
        }
        
        // ایجاد و اتصال SignalR
        signalRClient = object : FaceEmbeddingSignalRClient(workshopId) {
            override fun onEmbeddingCreated(employeeId: Long, employeeFullName: String, timestamp: String) {
                runOnUiThread {
                    // بروزرسانی UI
                    Toast.makeText(this@MainActivity, 
                        "Embedding ایجاد شد برای: $employeeFullName", 
                        Toast.LENGTH_SHORT).show()
                    
                    // دریافت داده‌های جدید از API
                    refreshEmployeeList()
                }
            }
            
            override fun onEmbeddingDeleted(employeeId: Long, timestamp: String) {
                runOnUiThread {
                    // بروزرسانی UI
                    refreshEmployeeList()
                }
            }
            
            override fun onEmbeddingRefined(employeeId: Long, timestamp: String) {
                runOnUiThread {
                    // بروزرسانی UI
                    refreshEmployeeList()
                }
            }
        }
        
        signalRClient.connect()
    }
    
    override fun onDestroy() {
        super.onDestroy()
        signalRClient.disconnect()
    }
    
    private fun refreshEmployeeList() {
        // دریافت لیست جدید کارمندان از API
    }
}

مثال ساده بدون Login:

اگر workshopId را از قبل می‌دانید:

Java:

public class MainActivity extends AppCompatActivity {
    private FaceEmbeddingSignalRClient signalRClient;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        long workshopId = 123; // شناسه کارگاه خود را وارد کنید
        
        signalRClient = new FaceEmbeddingSignalRClient(workshopId) {
            @Override
            protected void onEmbeddingCreated(long employeeId, String employeeFullName, String timestamp) {
                runOnUiThread(() -> {
                    // بروزرسانی UI
                    Toast.makeText(MainActivity.this, 
                        "Embedding ایجاد شد برای: " + employeeFullName, 
                        Toast.LENGTH_SHORT).show();
                    
                    // دریافت داده‌های جدید از API
                    refreshEmployeeList();
                });
            }
            
            @Override
            protected void onEmbeddingDeleted(long employeeId, String timestamp) {
                runOnUiThread(() -> {
                    // بروزرسانی UI
                    refreshEmployeeList();
                });
            }
        };
        
        signalRClient.connect();
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (signalRClient != null) {
            signalRClient.disconnect();
        }
    }
    
    private void refreshEmployeeList() {
        // دریافت لیست جدید کارمندان از API
    }
}

Kotlin:

class MainActivity : AppCompatActivity() {
    private lateinit var signalRClient: FaceEmbeddingSignalRClient
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        val workshopId = 123L // شناسه کارگاه خود را وارد کنید
        
        signalRClient = object : FaceEmbeddingSignalRClient(workshopId) {
            override fun onEmbeddingCreated(employeeId: Long, employeeFullName: String, timestamp: String) {
                runOnUiThread {
                    // بروزرسانی UI
                    Toast.makeText(this@MainActivity, 
                        "Embedding ایجاد شد برای: $employeeFullName", 
                        Toast.LENGTH_SHORT).show()
                    
                    // دریافت داده‌های جدید از API
                    refreshEmployeeList()
                }
            }
            
            override fun onEmbeddingDeleted(employeeId: Long, timestamp: String) {
                runOnUiThread {
                    // بروزرسانی UI
                    refreshEmployeeList()
                }
            }
        }
        
        signalRClient.connect()
    }
    
    override fun onDestroy() {
        super.onDestroy()
        signalRClient.disconnect()
    }
    
    private fun refreshEmployeeList() {
        // دریافت لیست جدید کارمندان از API
    }
}

5. نکات مهم

آدرس سرور

  • اگر روی شبیه‌ساز اندروید تست می‌کنید و سرور روی localhost اجرا می‌شود، از آدرس http://10.0.2.2:PORT استفاده کنید
  • اگر روی دستگاه فیزیکی تست می‌کنید، از آدرس IP شبکه محلی سرور استفاده کنید (مثل http://192.168.1.100:PORT)
  • PORT پیش‌فرض معمولاً 5000 یا 5001 است (بسته به کانفیگ پروژه شما)

دریافت WorkshopId از Login

بعد از login موفق، workshopId را از سرور دریافت کنید و در SharedPreferences یا یک Singleton ذخیره کنید:

// بعد از login موفق
SharedPreferences prefs = getSharedPreferences("AppPrefs", MODE_PRIVATE);
prefs.edit().putLong("workshopId", workshopId).apply();

// استفاده در Activity
long workshopId = prefs.getLong("workshopId", 0);

یا در Kotlin:

// بعد از login موفق
val prefs = getSharedPreferences("AppPrefs", Context.MODE_PRIVATE)
prefs.edit().putLong("workshopId", workshopId).apply()

// استفاده در Activity
val workshopId = prefs.getLong("workshopId", 0L)

مدیریت اتصال

برای reconnection خودکار:

hubConnection.onClosed(exception -> {
    Log.e(TAG, "Connection closed. Attempting to reconnect...");
    new Handler().postDelayed(() -> connect(), 5000); // تلاش مجدد بعد از 5 ثانیه
});

Thread Safety

همیشه UI updates را در main thread انجام دهید:

runOnUiThread(() -> {
    // UI updates here
});

6. تست اتصال

برای تست می‌توانید:

  1. اپلیکیشن را اجرا کنید
  2. از طریق Postman یا Swagger یک Embedding ایجاد کنید
  3. باید در Logcat پیام "Embedding Created" را ببینید

7. خطایابی (Debugging)

برای دیدن جزئیات بیشتر:

hubConnection = HubConnectionBuilder
    .create(serverUrl)
    .withHttpConnectionOptions(options -> {
        options.setLogging(LogLevel.TRACE);
    })
    .build();

خلاصه Endpoints

نوع رویداد متد SignalR پارامترهای دریافتی
ایجاد Embedding EmbeddingCreated workshopId, employeeId, employeeFullName, timestamp
حذف Embedding EmbeddingDeleted workshopId, employeeId, timestamp
بهبود Embedding EmbeddingRefined workshopId, employeeId, timestamp
متد ارسالی پارامتر توضیحات
JoinWorkshopGroup workshopId عضویت در گروه کارگاه
LeaveWorkshopGroup workshopId خروج از گروه کارگاه