portaldacalheta.pt
  • Κύριος
  • Επενδυτές & Χρηματοδότηση
  • Άνθρωποι Προϊόντων Και Ομάδες
  • Επιστήμη Δεδομένων Και Βάσεις Δεδομένων
  • Τάσεις
Κινητό

Πώς να απλοποιήσετε το Concurrency με το Reactive Modeling στο Android



Η ταυτόχρονη και ασυγχρονικότητα είναι εγγενής στον προγραμματισμό για κινητά.

Η εναρμόνιση με τον προγραμματισμό επιτακτικού τύπου, που περιλαμβάνει συνήθως ο προγραμματισμός στο Android, μπορεί να είναι η αιτία πολλών προβλημάτων. Χρήση του Reactive Programming με RxJava , μπορείτε να αποφύγετε πιθανά προβλήματα ταυτότητας παρέχοντας μια καθαρότερη και λιγότερο επιρρεπή σε σφάλματα λύση.



Εκτός από την απλοποίηση ταυτόχρονων, ασύγχρονων εργασιών, το RxJava παρέχει επίσης τη δυνατότητα εκτέλεσης λειτουργικών λειτουργικών στυλ που μετασχηματίζουν, συνδυάζουν και συγκεντρώνουν εκπομπές από ένα Παρατηρήσιμο έως ότου επιτύχουμε το επιθυμητό αποτέλεσμα.





Συνδυάζοντας το αντιδραστικό παράδειγμα του RxJava και τις λειτουργικές λειτουργικές λειτουργίες, μπορούμε να μοντελοποιήσουμε ένα ευρύ φάσμα κατασκευών ταυτόχρονης με έναν αντιδραστικό τρόπο, ακόμη και στον μη αντιδραστικό κόσμο του Android. Σε αυτό το άρθρο, θα μάθετε πώς μπορείτε να το κάνετε ακριβώς αυτό. Θα μάθετε επίσης πώς να υιοθετήσετε το RxJava σε ένα υπάρχον έργο σταδιακά.

Εάν είστε νέοι στο RxJava, προτείνω να διαβάσετε την ανάρτηση εδώ που μιλά για μερικά από τα βασικά του RxJava.



Γέφυρα μη αντιδραστικών στον αντιδραστικό κόσμο

Μία από τις προκλήσεις της προσθήκης του RxJava ως μιας από τις βιβλιοθήκες στο έργο σας είναι ότι αλλάζει ριζικά τον τρόπο με τον οποίο σκέφτεστε για τον κώδικά σας.

Αριθμομηχανή χρέωσης έναντι μισθού

Το RxJava απαιτεί από εσάς να σκεφτείτε ότι τα δεδομένα ωθούνται αντί να τραβούν. Ενώ η ίδια η ιδέα είναι απλή, η αλλαγή μιας πλήρους βάσης κώδικα που βασίζεται σε ένα πρότυπο έλξης μπορεί να είναι λίγο τρομακτική. Παρόλο που η συνέπεια είναι πάντα ιδανική, ίσως να μην έχετε πάντα το προνόμιο να κάνετε αυτήν τη μετάβαση σε ολόκληρη τη βάση κώδικα ταυτόχρονα, οπότε μπορεί να χρειαστεί περισσότερη σταδιακή προσέγγιση.



Εξετάστε τον ακόλουθο κώδικα:

/** * @return a list of users with blogs */ public List getUsersWithBlogs() { final List allUsers = UserCache.getAllUsers(); final List usersWithBlogs = new ArrayList(); for (User user : allUsers) { if (user.blog != null && !user.blog.isEmpty()) { usersWithBlogs.add(user); } } Collections.sort(usersWithBlogs, (user1, user2) -> user1.name.compareTo(user2.name)); return usersWithBlogs; }

Αυτή η συνάρτηση λαμβάνει μια λίστα User αντικείμενα από την προσωρινή μνήμη, φιλτράρει το καθένα με βάση το αν ο χρήστης έχει ιστολόγιο ή όχι, τα ταξινομεί με το όνομα του χρήστη και τελικά τα επιστρέφει στον καλούντα. Κοιτάζοντας αυτό το απόσπασμα, παρατηρούμε ότι πολλές από αυτές τις λειτουργίες μπορούν να επωφεληθούν από τους χειριστές RxJava. π.χ. filter() και sorted().



Η επανεγγραφή αυτού του αποσπάσματος μας δίνει έπειτα:

/** * @return a list of users with blogs */ public Observable getUsersWithBlogs() { return Observable.fromIterable(UserCache.getAllUsers()) .filter(user -> user.blog != null && !user.blog.isEmpty()) .sorted((user1, user2) -> user1.name.compareTo(user2.name)); }

Η πρώτη γραμμή της συνάρτησης μετατρέπει το List επέστρεψε από UserCache.getAllUsers() σε ένα Observable μέσω fromIterable(). Αυτό είναι το πρώτο βήμα για να κάνουμε τον κώδικα αντιδραστικό. Τώρα που λειτουργούμε σε ένα Observable, αυτό μας δίνει τη δυνατότητα να εκτελέσουμε οποιαδήποτε Observable τελεστής στο κιτ εργαλείων RxJava - filter() και sorted() σε αυτήν την περίπτωση.



Υπάρχουν μερικά άλλα σημεία που πρέπει να σημειώσετε σχετικά με αυτήν την αλλαγή.

Πρώτον, η υπογραφή της μεθόδου δεν είναι πλέον η ίδια. Αυτό μπορεί να μην είναι τεράστιο εάν η κλήση αυτής της μεθόδου χρησιμοποιείται μόνο σε λίγα μέρη και είναι εύκολο να διαδώσετε τις αλλαγές σε άλλες περιοχές της στοίβας. Ωστόσο, εάν σπάσει τους πελάτες που βασίζονται σε αυτήν τη μέθοδο, αυτό είναι προβληματικό και η υπογραφή της μεθόδου θα πρέπει να αντιστραφεί.



Δεύτερον, το RxJava έχει σχεδιαστεί λαμβάνοντας υπόψη την τεμπελιά. Δηλαδή, δεν πρέπει να εκτελούνται μακροχρόνιες λειτουργίες όταν δεν υπάρχουν συνδρομητές στο Observable. Με αυτήν την τροποποίηση, αυτή η υπόθεση δεν ισχύει πλέον αφού UserCache.getAllUsers() καλείται ακόμη και πριν υπάρξουν συνδρομητές.

Αφήνοντας τον Αντιδραστικό Κόσμο

Για να αντιμετωπίσουμε το πρώτο ζήτημα από την αλλαγή μας, μπορούμε να χρησιμοποιήσουμε οποιονδήποτε από τους τελεστές αποκλεισμού που διατίθενται σε ένα Observable όπως blockingFirst() και blockingNext(). Ουσιαστικά, και οι δύο αυτοί τελεστές θα μπλοκάρουν έως ότου ένα στοιχείο εκπέμπεται κατάντη: blockingFirst() θα επιστρέψει το πρώτο στοιχείο που εκπέμπεται και θα τελειώσει, ενώ blockingNext() θα επιστρέψει ένα Iterable το οποίο σας επιτρέπει να εκτελέσετε έναν βρόχο για κάθε υποκείμενο δεδομένα (κάθε επανάληψη μέσω του βρόχου θα μπλοκάρει).

Ωστόσο, μια παρενέργεια της χρήσης μιας λειτουργίας αποκλεισμού που είναι σημαντικό να γνωρίζετε είναι ότι οι εξαιρέσεις ρίχνονται στο νήμα κλήσης αντί να μεταβιβαστούν σε έναν παρατηρητή onError() μέθοδος.

Χρησιμοποιώντας έναν τελεστή αποκλεισμού για να αλλάξετε την υπογραφή της μεθόδου σε ένα List, το απόσπασμα μας θα μοιάζει τώρα με αυτό:

/** * @return a list of users with blogs */ public List getUsersWithBlogs() { return Observable.fromIterable(UserCache.getAllUsers()) .filter(user -> user.blog != null && !user.blog.isEmpty()) .sorted((user1, user2) -> user1.name.compareTo(user2.name)) .toList() .blockingGet(); }

Πριν καλέσουμε έναν τελεστή αποκλεισμού (δηλαδή blockingGet()) πρέπει πρώτα να αλυσολογήσουμε τον αθροιστικό τελεστή toList() έτσι ώστε η ροή να τροποποιηθεί από ένα Observable σε ένα Single (a Single είναι ένας ειδικός τύπος Observable που εκπέμπει μόνο μία τιμή στο onSuccess() ή ένα σφάλμα μέσω onError()).

Στη συνέχεια, μπορούμε να καλέσουμε τον τελεστή αποκλεισμού blockingGet() που ξετυλίγει το Single και επιστρέφει ένα List.

Παρόλο που το RxJava το υποστηρίζει, πρέπει να αποφεύγεται όσο το δυνατόν περισσότερο, καθώς αυτός δεν είναι ιδιωματικός αντιδραστικός προγραμματισμός. Όμως, όταν είναι απολύτως απαραίτητο, οι τελεστές αποκλεισμού είναι ένας καλός αρχικός τρόπος για να βγείτε από τον αντιδραστικό κόσμο.

Η Τεμπέλη Προσέγγιση

Όπως αναφέρθηκε προηγουμένως, το RxJava σχεδιάστηκε με γνώμονα την τεμπελιά. Δηλαδή, οι μακροχρόνιες λειτουργίες θα πρέπει να καθυστερούν όσο το δυνατόν περισσότερο (δηλαδή, έως ότου καλείται μια εγγραφή σε ένα Observable). Για να κάνουμε τη λύση τεμπέλης, χρησιμοποιούμε το defer() χειριστής.

defer() παίρνει ένα ObservableSource εργοστάσιο που δημιουργεί ένα Observable για κάθε νέο παρατηρητή που εγγράφεται. Στην περίπτωσή μας, θέλουμε να επιστρέψουμε Observable.fromIterable(UserCache.getAllUser()) όποτε εγγραφεί ένας παρατηρητής.

/** * @return a list of users with blogs */ public Observable getUsersWithBlogs() { return Observable.defer(() -> Observable.fromIterable(UserCache.getAllUsers())) .filter(user -> user.blog != null && !user.blog.isEmpty()) .sorted((user1, user2) -> user1.name.compareTo(user2.name)); }

Τώρα που η μακροχρόνια λειτουργία είναι τυλιγμένη σε defer(), έχουμε τον πλήρη έλεγχο ως προς το σε ποιο νήμα πρέπει να τρέχει απλά καθορίζοντας το κατάλληλο Scheduler σε subscribeOn(). Με αυτήν την αλλαγή, ο κώδικάς μας είναι πλήρως αντιδραστικός και η εγγραφή θα πρέπει να πραγματοποιείται μόνο τη στιγμή που απαιτούνται τα δεδομένα.

/** * @return a list of users with blogs */ public Observable getUsersWithBlogs() { return Observable.defer(() -> Observable.fromIterable(UserCache.getAllUsers())) .filter(user -> user.blog != null && !user.blog.isEmpty()) .sorted((user1, user2) -> user1.name.compareTo(user2.name)) .subscribeOn(Schedulers.io()); }

Ένας άλλος πολύ χρήσιμος χειριστής για την αναβολή του υπολογισμού είναι το fromCallable() μέθοδος. Σε αντίθεση με το defer(), το οποίο αναμένει ένα Observable για να επιστραφεί στη συνάρτηση λάμδα και με τη σειρά 'flattens' το επιστρεφόμενο Observable, fromCallable() θα επικαλεστεί το λάμδα και θα επιστρέψει την τιμή κατάντη.

/** * @return a list of users with blogs */ public Observable getUsersWithBlogs() { final Observable usersObservable = Observable.fromCallable(() -> UserCache.getAllUsers()); final Observable userObservable = usersObservable.flatMap(users -> Observable.fromIterable(users)); return userObservable.filter(user -> user.blog != null && !user.blog.isEmpty()) .sorted((user1, user2) -> user1.name.compareTo(user2.name)); }

Μεμονωμένη χρήση fromCallable() σε μια λίστα θα επιστρέψει τώρα Observable, πρέπει να ισοπεδώσουμε αυτήν τη λίστα χρησιμοποιώντας flatMap().

Αντιδραστικό - τα πάντα

Από τα προηγούμενα παραδείγματα, έχουμε δει ότι μπορούμε να τυλίξουμε οποιοδήποτε αντικείμενο σε ένα Observable και μεταβείτε μεταξύ μη αντιδραστικών και αντιδραστικών καταστάσεων χρησιμοποιώντας λειτουργίες αποκλεισμού και defer() / fromCallable(). Χρησιμοποιώντας αυτές τις κατασκευές, μπορούμε να ξεκινήσουμε τη μετατροπή περιοχών μιας εφαρμογής Android για να είναι αντιδραστικές.

Μακροχρόνιες λειτουργίες

Ένα καλό μέρος για να σκεφτείτε αρχικά τη χρήση του RxJava είναι όποτε έχετε μια διαδικασία που απαιτεί λίγο χρόνο για να εκτελέσετε, όπως κλήσεις δικτύου (δείτε προηγούμενη ανάρτηση για παραδείγματα), ο δίσκος διαβάζει και γράφει, κ.λπ. Το ακόλουθο παράδειγμα απεικονίζει μια απλή συνάρτηση που θα γράφει κείμενο στο σύστημα αρχείων:

/** * Writes {@code text} to the file system. * * @param context a Context * @param filename the name of the file * @param text the text to write * @return true if the text was successfully written, otherwise, false */ public boolean writeTextToFile(Context context, String filename, String text) { FileOutputStream outputStream; try { outputStream = context.openFileOutput(filename, Context.MODE_PRIVATE); outputStream.write(text.getBytes()); outputStream.close(); return true; } catch (Exception e) { e.printStackTrace(); return false; } }

Κατά την κλήση αυτής της λειτουργίας, πρέπει να βεβαιωθούμε ότι γίνεται σε ξεχωριστό νήμα, καθώς αυτή η λειτουργία αποκλείει. Η επιβολή ενός τέτοιου περιορισμού στον καλούντα περιπλέκει τα πράγματα για τον προγραμματιστή, γεγονός που αυξάνει την πιθανότητα σφαλμάτων και μπορεί ενδεχομένως να επιβραδύνει την ανάπτυξη.

Η προσθήκη σχολίου στη λειτουργία θα βοηθήσει φυσικά στην αποφυγή σφαλμάτων από τον καλούντα, αλλά αυτό απέχει πολύ από την αλεξίσφαιρα.

Χρησιμοποιώντας το RxJava, ωστόσο, μπορούμε εύκολα να το τυλίξουμε σε ένα Observable και καθορίστε το Scheduler ότι πρέπει να συνεχίσει. Με αυτόν τον τρόπο, ο καλών δεν χρειάζεται να ανησυχεί καθόλου για την επίκληση της λειτουργίας σε ξεχωριστό νήμα. η λειτουργία θα το φροντίσει το ίδιο.

/** * Writes {@code text} to the filesystem. * * @param context a Context * @param filename the name of the file * @param text the text to write * @return An Observable emitting a boolean indicating whether or not the text was successfully written. */ public Observable writeTextToFile(Context context, String filename, String text) { return Observable.fromCallable(() -> { FileOutputStream outputStream; outputStream = context.openFileOutput(filename, Context.MODE_PRIVATE); outputStream.write(text.getBytes()); outputStream.close(); return true; }).subscribeOn(Schedulers.io()); }

Χρησιμοποιώντας το fromCallable(), η σύνταξη του κειμένου στο αρχείο αναβάλλεται έως την ώρα της συνδρομής.

Δεδομένου ότι οι εξαιρέσεις είναι αντικείμενα πρώτης κατηγορίας στο RxJava, ένα άλλο πλεονέκτημα της αλλαγής μας είναι ότι δεν χρειάζεται πλέον να ολοκληρώσουμε τη λειτουργία σε ένα μπλοκ try / catch. Η εξαίρεση απλώς θα μεταδοθεί κατάντη αντί να καταπιεί. Αυτό επιτρέπει στον καλούντα να χειριστεί την εξαίρεση που θεωρεί κατάλληλη (π.χ. να δείξει ένα σφάλμα στον χρήστη ανάλογα με την εξαίρεση που ρίχτηκε κ.λπ.).

Μια άλλη βελτιστοποίηση που μπορούμε να κάνουμε είναι να επιστρέψουμε ένα Completable αντί για Observable. Α Completable είναι ουσιαστικά ένας ειδικός τύπος Observable - παρόμοιο με ένα Single - αυτό δείχνει απλώς εάν ένας υπολογισμός πέτυχε, μέσω onComplete(), ή απέτυχε, μέσω onError(). Επιστροφή a Completable φαίνεται να έχει πιο νόημα σε αυτήν την περίπτωση, καθώς φαίνεται ανόητο να επιστρέψουμε ένα αληθινό σε ένα Observable ρεύμα.

/** * Writes {@code text} to the filesystem. * * @param context a context * @param filename the name of the file * @param text the text to write * @return A Completable */ public Completable writeTextToFile(Context context, String filename, String text) { return Completable.fromAction(() -> { FileOutputStream outputStream; outputStream = context.openFileOutput(filename, Context.MODE_PRIVATE); outputStream.write(text.getBytes()); outputStream.close(); }).subscribeOn(Schedulers.io()); }

Για να ολοκληρώσουμε τη λειτουργία, χρησιμοποιούμε το fromAction() λειτουργία ενός Completable αφού η αξία επιστροφής δεν μας ενδιαφέρει πλέον. Εάν χρειαστεί, όπως ένα Observable, a Completable υποστηρίζει επίσης το fromCallable() και defer() λειτουργίες.

Αντικατάσταση κλήσεων

Μέχρι στιγμής, όλα τα παραδείγματα που εξετάσαμε εκπέμπουν είτε μία τιμή (δηλαδή, μπορούν να μοντελοποιηθούν ως Single), είτε να μας πείτε εάν μια λειτουργία πέτυχε ή απέτυχε (δηλαδή, μπορεί να μοντελοποιηθεί ως Completable).

Πώς θα μπορούσαμε όμως να μετατρέψουμε περιοχές στην εφαρμογή μας, οι οποίες λαμβάνουν συνεχείς ενημερώσεις ή συμβάντα (όπως ενημερώσεις τοποθεσίας, προβολή συμβάντων κλικ, συμβάντα αισθητήρα κ.λπ.);

Θα εξετάσουμε δύο τρόπους για να το κάνουμε αυτό, χρησιμοποιώντας το create() και χρήση Subjects.

create() μας επιτρέπει να επικαλεστούμε ρητά το παρατηρητή onNext() | onComplete() | onError() μέθοδος καθώς λαμβάνουμε ενημερώσεις από την πηγή δεδομένων μας. Για να χρησιμοποιήσετε το create(), περνάμε σε ένα ObservableOnSubscribe που λαμβάνει ένα ObservableEmitter όποτε εγγραφεί ένας παρατηρητής. Χρησιμοποιώντας τον λαμβανόμενο πομπό, μπορούμε στη συνέχεια να εκτελέσουμε όλες τις απαραίτητες κλήσεις ρύθμισης για να αρχίσουμε να λαμβάνουμε ενημερώσεις και στη συνέχεια να επικαλεστούμε το κατάλληλο Emitter Εκδήλωση.

Σημείο πώλησης με βάση το Android

Στην περίπτωση ενημερώσεων τοποθεσίας, μπορούμε να εγγραφούμε για να λαμβάνουμε ενημερώσεις σε αυτό το μέρος και να εκπέμπουμε ενημερώσεις τοποθεσίας όπως ελήφθησαν.

public class LocationManager { /** * Call to receive device location updates. * @return An Observable emitting location updates */ public Observable observeLocation() { return Observable.create(emitter -> { // Make sure that the following conditions apply and if not, call the emitter's onError() method // (1) googleApiClient is connected // (2) location permission is granted final LocationRequest locationRequest = new LocationRequest(); locationRequest.setInterval(1000); locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, new LocationListener() { @Override public void onLocationChanged(Location location) { if (!emitter.isDisposed()) { emitter.onNext(location); } } }); }); } }

Η συνάρτηση μέσα στο create() η κλήση ζητά ενημερώσεις τοποθεσίας και μεταβιβάζεται σε μια επιστροφή κλήσης που καλείται όταν αλλάζει η τοποθεσία της συσκευής. Όπως μπορούμε να δούμε εδώ, αντικαθιστούμε ουσιαστικά τη διεπαφή τύπου επανάκλησης και αντ 'αυτού εκπέμπουμε τη ληφθείσα τοποθεσία στη δημιουργούμενη ροή Παρατήρησης (για λόγους εκπαιδευτικούς σκοπούς, παραλείψαμε μερικές από τις λεπτομέρειες με την κατασκευή ενός αιτήματος τοποθεσίας, εάν θέλετε να διαγράψετε βαθύτερα στις λεπτομέρειες μπορείτε να το διαβάσετε εδώ ).

Ένα άλλο πράγμα που πρέπει να σημειωθεί σχετικά με create() είναι αυτό, όποτε subscribe() καλείται, παρέχεται ένας νέος πομπός. Με άλλα λόγια, create() επιστρέφει ένα κρύο Observable . Αυτό σημαίνει ότι, στην παραπάνω συνάρτηση, ενδεχομένως να ζητάμε ενημερώσεις τοποθεσίας πολλές φορές, κάτι που δεν είναι αυτό που θέλουμε.

Για να επιλύσουμε αυτό, θέλουμε να αλλάξουμε τη συνάρτηση για να επιστρέψουμε μια καυτή Observable με τη βοήθεια του Subjects.

Εισαγάγετε θέματα

Α Subject επεκτείνει ένα Observable και υλοποιεί Observer Την ίδια στιγμή. Αυτό είναι ιδιαίτερα χρήσιμο όποτε θέλουμε να εκπέμψουμε ή να μεταδώσουμε το ίδιο συμβάν σε πολλούς συνδρομητές ταυτόχρονα. Κατά την εφαρμογή, θα θέλαμε να εκθέσουμε το Subject ως Observable στους πελάτες, διατηρώντας το ως Subject για τον πάροχο.

public class LocationManager { private Subject locationSubject = PublishSubject.create(); /** * Invoke this method when this LocationManager should start listening to location updates. */ public void connect() { final LocationRequest locationRequest = new LocationRequest(); locationRequest.setInterval(1000); locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, locationRequest, new LocationListener() { @Override public void onLocationChanged(Location location) { locationSubject.onNext(location); } }); } /** * Call to receive device location updates. * @return An Observable emitting location updates */ public Observable observeLocation() { return locationSubject; } }

Σε αυτήν τη νέα εφαρμογή, ο υποτύπος PublishSubject χρησιμοποιείται το οποίο εκπέμπει συμβάντα καθώς φθάνουν ξεκινώντας από τη στιγμή της συνδρομής. Κατά συνέπεια, εάν μια συνδρομή εκτελείται σε ένα σημείο που έχουν ήδη εκδοθεί ενημερώσεις τοποθεσίας, οι προηγούμενες εκπομπές δεν θα λαμβάνονται από τον παρατηρητή, παρά μόνο μεταγενέστερες. Εάν αυτή η συμπεριφορά δεν είναι επιθυμητή, υπάρχουν μερικά άλλα Subject υπότυποι στην εργαλειοθήκη RxJava που μπορεί να είναι μεταχειρισμένος .

Επιπλέον, δημιουργήσαμε επίσης ένα ξεχωριστό connect() συνάρτηση που ξεκινά το αίτημα για λήψη ενημερώσεων τοποθεσίας. Το observeLocation() μπορεί ακόμα να κάνει το connect() καλέστε, αλλά το επαναπροσδιορίσαμε από τη λειτουργία για σαφήνεια / απλότητα.

Περίληψη

Εξετάσαμε έναν αριθμό μηχανισμών και τεχνικών:

  • defer() και τις παραλλαγές του για να καθυστερήσει την εκτέλεση ενός υπολογισμού μέχρι τη συνδρομή
  • κρύο Observables δημιουργήθηκε μέσω create()
  • καυτό Observables χρησιμοποιώντας Subjects
  • λειτουργίες blockingX όταν θέλουμε να αφήσουμε τον αντιδραστικό κόσμο

Ας ελπίσουμε ότι τα παραδείγματα που παρέχονται σε αυτό το άρθρο ενέπνευσαν ορισμένες ιδέες σχετικά με διαφορετικούς τομείς στην εφαρμογή σας που μπορούν να μετατραπούν σε αντιδραστικές. Έχουμε καλύψει πολλά και εάν έχετε οποιεσδήποτε ερωτήσεις, προτάσεις ή εάν κάτι δεν είναι σαφές, μη διστάσετε να αφήσετε ένα σχόλιο παρακάτω!

Εάν ενδιαφέρεστε να μάθετε περισσότερα για το RxJava, εργάζομαι σε ένα σε βάθος βιβλίο που εξηγεί πώς να βλέπω προβλήματα με τον αντιδραστικό τρόπο χρησιμοποιώντας παραδείγματα Android. Εάν θέλετε να λαμβάνετε ενημερώσεις σχετικά με αυτό, εγγραφείτε εδώ .

Εξηγούμενα ευρετήρια SQL, Pt. 1

Πίσω Μέρος

Εξηγούμενα ευρετήρια SQL, Pt. 1
Ο Αμερικανός προγραμματιστής Rachell Calhoun κερδίζει την πέμπτη υποτροφία ApeeScape

Ο Αμερικανός προγραμματιστής Rachell Calhoun κερδίζει την πέμπτη υποτροφία ApeeScape

Αλλα

Δημοφιλείς Αναρτήσεις
Κάντε τις εφαρμογές Math: Scaling Microservices με ενορχηστρωτές
Κάντε τις εφαρμογές Math: Scaling Microservices με ενορχηστρωτές
Πώς να προβλέψετε το μέλλον: Μετριασμός των επιπτώσεων της αβεβαιότητας
Πώς να προβλέψετε το μέλλον: Μετριασμός των επιπτώσεων της αβεβαιότητας
Ακονίστε τις δεξιότητές σας: Η αξία του πολυτομεακού σχεδιασμού
Ακονίστε τις δεξιότητές σας: Η αξία του πολυτομεακού σχεδιασμού
Η σημασία του UX και του Design Thinking
Η σημασία του UX και του Design Thinking
Θάνατος στα Wireframes. Ας πάμε κατευθείαν στην υψηλή πιστότητα!
Θάνατος στα Wireframes. Ας πάμε κατευθείαν στην υψηλή πιστότητα!
 
Αντικείμενα υπηρεσίας σιδηροτροχιών: Ένας περιεκτικός οδηγός
Αντικείμενα υπηρεσίας σιδηροτροχιών: Ένας περιεκτικός οδηγός
Η εμπειρία είναι τα πάντα - Ο απόλυτος οδηγός UX
Η εμπειρία είναι τα πάντα - Ο απόλυτος οδηγός UX
Είναι επικερδείς οι προσπάθειες εταιρικής ευθύνης;
Είναι επικερδείς οι προσπάθειες εταιρικής ευθύνης;
Συζητήσεις σχεδιασμού: Καλύτερη συνεργασία σχεδιαστών και προγραμματιστών με τον Aarron Walter της InVision
Συζητήσεις σχεδιασμού: Καλύτερη συνεργασία σχεδιαστών και προγραμματιστών με τον Aarron Walter της InVision
Εισαγωγή στη θεωρία και την πολυπλοκότητα της υπολογιστικότητας
Εισαγωγή στη θεωρία και την πολυπλοκότητα της υπολογιστικότητας
Δημοφιλείς Αναρτήσεις
  • σε τι χρησιμοποιείται το docx
  • πόσο μεγάλη είναι η βιομηχανία καλλυντικών
  • έργα διακομιστή raspberry pi 3
  • πόσες τοποθεσίες γνωριμιών υπάρχουν
  • λυσεις aws architect associate exam
  • πλεονεκτήματα εταιρικής σχέσης έναντι εταιρικού φόρου
  • πώς να προετοιμάσετε την τεκμηρίωση για ένα έργο λογισμικού
Κατηγορίες
  • Επενδυτές & Χρηματοδότηση
  • Άνθρωποι Προϊόντων Και Ομάδες
  • Επιστήμη Δεδομένων Και Βάσεις Δεδομένων
  • Τάσεις
  • © 2022 | Ολα Τα Δικαιώματα Διατηρούνται

    portaldacalheta.pt